Controles de formulario personalizados angulares

Cuando planeamos personalizar los controles de formulario, primero debemos considerar las siguientes preguntas:

  • ¿Tiene elementos nativos con la misma semántica? Como:<input type="number">

  • Si es así, debemos considerar si podemos confiar en el elemento y cambiar su apariencia / comportamiento usando CSS o una mejora incremental para satisfacer nuestras necesidades.

  • Si no es así, ¿cómo se verá el control personalizado?

  • ¿Cómo lo hacemos accesible?

  • ¿El comportamiento de los controles personalizados es diferente en diferentes plataformas?

  • ¿Cómo realiza el control personalizado la función de validación de datos?

Puede haber muchas cosas a considerar, pero si decidimos usar Angular para crear controles personalizados, debemos considerar los siguientes problemas:

  • ¿Cómo implementar modelo -> ver enlace de datos?

  • ¿Cómo lograr la vista -> sincronización de datos del modelo?

  • Si se requiere autenticación personalizada, ¿cómo se debe implementar?

  • ¿Cómo agregar estado de validez a los elementos DOM para facilitar la configuración de diferentes estilos?

  • ¿Cómo hacer que el control sea accesible (accesible)?

  • ¿Se puede aplicar este control a formularios basados ​​en plantillas?

  • ¿Se puede aplicar este control a formularios controlados por modelos?

(Nota: el estado actual de compatibilidad de accesibilidad de HTML 5 en los navegadores principales, consulte  Accesibilidad de HTML5 )

Creando un contador personalizado

Ahora comenzamos con el componente Counter más simple, el código específico es el siguiente:

counter.component.ts

import { Component, Input } from '@angular/core';

@Component({
    selector: 'exe-counter',
    template: `
    <div>
      <p>当前值: {
   
   { count }}</p>
      <button (click)="increment()"> + </button>
      <button (click)="decrement()"> - </button>
    </div>
    `
})
export class CounterComponent {
    @Input() count: number = 0;

    increment() {
        this.count++;
    }

    decrement() {
        this.count--;
    }
}

app.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'exe-app',
  template: `
    <exe-counter></exe-counter>
  `,
})
export class AppComponent { }

app.module.ts

import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { CounterComponent } from './couter.component';
import { AppComponent } from './app.component';

@NgModule({
  imports: [BrowserModule],
  declarations: [AppComponent, CounterComponent],
  bootstrap: [AppComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }

Muy bien, pronto se implementó el componente CounterComponent. Pero ahora queremos  usar este componente en el  formulario Template-Driven o  Reactive, de la siguiente manera:

<!-- this doesn't work YET -->
<form #form="ngForm">
  <exe-counter name="counter" ngModel></exe-counter>
  <button type="submit">Submit</button>
</form>

Ahora no podemos usarlo directamente de esta manera, necesitamos realizar esta función. Primero tenemos ControlValueAccessorque resolverlo  , porque es un puente entre el modelo de formulario y los elementos DOM.

Comprensión de ControlValueAccessor

Cuando ejecutamos el ejemplo anterior, la siguiente información de excepción aparecerá en la consola del navegador:

Uncaught (in promise): Error: No value accessor for form control with name: 'counter'

Entonces, ¿ ControlValueAccessor qué es? Entonces, ¿recuerdas las cosas que mencionamos antes para confirmar la implementación de controles personalizados? Una de las cosas por confirmar es darse cuenta de la vinculación de datos entre Modelo -> Vista y Vista -> Modelo, y este es el problema con el que nuestro ControlValueAccessor tiene que lidiar.

ControlValueAccessor es una interfaz, su función es:

  • Asignar los valores en el modelo de formulario a la vista

  • Cuando cambie la vista, notifique las directivas de formulario o los controles de formulario

La razón por la que Angular introdujo esta interfaz es que los diferentes métodos de actualización de datos de control de entrada son diferentes. Por ejemplo, para nuestro cuadro de entrada de texto de uso común, establecemos su  value valor, y para una casilla de verificación (casilla de verificación) establecemos su  checked atributo. De hecho, hay uno ControlValueAccessorpara diferentes tipos de controles de entrada  para actualizar la vista.

Los ControlValueAccessors comunes en Angular son:

  • DefaultValueAccessor: usado para  text y  textarea tipo controles de entrada

  • SelectControlValueAccessor: se usa para  select seleccionar controles

  • CheckboxControlValueAccessor: utilizado para el checkbox control de la casilla de  verificación

A continuación, nuestro componente CounterComponent necesita implementar la  ControlValueAccessor interfaz para que podamos actualizar el valor del recuento en el componente y notificar al mundo exterior que el valor ha cambiado.

Implementación de ControlValueAccessor

Primero, echemos un vistazo a la  ControlValueAccessor interfaz, como sigue:

// angular2/packages/forms/src/directives/control_value_accessor.ts 
export interface ControlValueAccessor {
  writeValue(obj: any): void;
  registerOnChange(fn: any): void;
  registerOnTouched(fn: any): void;
  setDisabledState?(isDisabled: boolean): void;
}
  • writeValue (obj: any): este método se utiliza para escribir el nuevo valor en el modelo en la vista o atributo DOM.

  • registerOnChange (fn: any): establece la función que se llamará cuando el control reciba el evento de cambio

  • registerOnTouched (fn: any): establece la función que se llamará cuando el control reciba el evento tocado

  • setDisabledState? (isDisabled: boolean):  Esta función se llama cuando el estado de control cambia  DISABLED o DISABLED cambia de un  estado a  ENABLEotro. Esta función habilitará o deshabilitará el elemento DOM especificado según el valor del parámetro.

A continuación, primero implementamos el  writeValue() método:

@Component(...)
class CounterComponent implements ControlValueAccessor {
  ...
  writeValue(value: any) {
    this.counterValue = value;
  }
}

Cuando se inicializa el formulario, el valor inicial correspondiente en el modelo de formulario se utilizará como parámetro para llamar al  writeValue() método. Esto significa que anulará el valor predeterminado de 0 y todo parece estar bien. Pero recordemos el uso esperado del componente CounterComponent en la forma:

<form #form="ngForm">
  <exe-counter name="counter" ngModel></exe-counter>
  <button type="submit">Submit</button>
</form>

Encontrará que no establecimos el valor inicial para el componente CounterComponent, por lo que debemos ajustar el código en writeValue () de la siguiente manera:

writeValue(value: any) {
  if (value) {
    this.count = value;
  }
}

Ahora, solo cuando se escribe un valor legal (no indefinido, nulo, "") en el control, se anulará el valor predeterminado. A continuación, vamos a poner en práctica  registerOnChange() y  registerOnTouched() método. registerOnChange () se puede usar para notificar al exterior que el componente ha cambiado. El método registerOnChange () recibe un  fn parámetro, que se utiliza para establecer la función que se llamará cuando el control reciba el evento de cambio. Para el método registerOnTouched (), también admite un  fn parámetro para configurar la función que se llamará cuando el control reciba un evento tocado. En el ejemplo no pretendemos manejar  touched eventos, por lo que configuramos registerOnTouched () como una función vacía. detalles como sigue:

@Component(...)
class CounterComponent implements ControlValueAccessor {
  ...
  propagateChange = (_: any) => {};

  registerOnChange(fn: any) {
    this.propagateChange = fn;
  }

  registerOnTouched(fn: any) {}
}

Muy bien, nuestro componente CounterComponent ha implementado la interfaz ControlValueAccessor. Lo siguiente que debemos hacer es llamar al método propagateChange () cada vez que cambia el valor de count. En otras palabras, cuando el usuario hace clic en el   botón + o  -, queremos pasar el nuevo valor al exterior.

@Component(...)
export class CounterComponent implements ControlValueAccessor {
    ...
    increment() {
        this.count++;
        this.propagateChange(this.count);
    }

    decrement() {
        this.count--;
        this.propagateChange(this.count);
    }
}

¿Crees que el código anterior es un poco redundante? A continuación, usemos el modificador de atributo para reconstruir el código anterior, de la siguiente manera:

counter.component.ts

import { Component, Input } from '@angular/core';
import { ControlValueAccessor } from '@angular/forms';

@Component({
    selector: 'exe-counter',
    template: `
      <p>当前值: {
   
   { count }}</p>
      <button (click)="increment()"> + </button>
      <button (click)="decrement()"> - </button>
    `
})
export class CounterComponent implements ControlValueAccessor {
    @Input() _count: number = 0;

    get count() {
        return this._count;
    }

    set count(value: number) {
        this._count = value;
        this.propagateChange(this._count);
    }

    propagateChange = (_: any) => { };

    writeValue(value: any) {
        if (value !== undefined) {
            this.count = value;
        }
    }

    registerOnChange(fn: any) {
        this.propagateChange = fn;
    }

    registerOnTouched(fn: any) { }

    increment() {
        this.count++;
    }

    decrement() {
        this.count--;
    }
}

El componente CounterComponent se ha desarrollado básicamente, pero si se puede utilizar normalmente, es necesario registrarlo.

Registro del ControlValueAccessor

Para el componente CounterComponent que desarrollamos, la implementación de la interfaz ControlValueAccessor solo completó la mitad del trabajo. Para permitir que Angular reconozca nuestra costumbre  ControlValueAccessor, también necesitamos realizar una operación de registro. El método específico es el siguiente:

  • Paso 1: cree EXE_COUNTER_VALUE_ACCESSOR

import { Component, Input, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

export const EXE_COUNTER_VALUE_ACCESSOR: any = {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => CounterComponent),
    multi: true
};

Recordatorio amistoso: para obtener información detallada sobre forwardRef y multi, consulte  estos dos artículos Angular 2 Forward Reference  y  Angular 2 Multi Providers .

  • Paso 2: establecer la información del proveedor del componente

@Component({
    selector: 'exe-counter',
    ...
    providers: [EXE_COUNTER_VALUE_ACCESSOR]
})

Todo está listo, solo le debemos a Dongfeng, ingresamos de inmediato al enlace de combate real para probar los CounterComponent componentes que desarrollamos  . El código completo es el siguiente:

counter.component.ts

import { Component, Input, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

export const EXE_COUNTER_VALUE_ACCESSOR: any = {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => CounterComponent),
    multi: true
};

@Component({
    selector: 'exe-counter',
    template: `
    <div>
      <p>当前值: {
   
   { count }}</p>
      <button (click)="increment()"> + </button>
      <button (click)="decrement()"> - </button>
    </div>
    `,
    providers: [EXE_COUNTER_VALUE_ACCESSOR]
})
export class CounterComponent implements ControlValueAccessor {
    @Input() _count: number = 0;

    get count() {
        return this._count;
    }

    set count(value: number) {
        this._count = value;
        this.propagateChange(this._count);
    }

    propagateChange = (_: any) => { };

    writeValue(value: any) {
        if (value) {
            this.count = value;
        }
    }

    registerOnChange(fn: any) {
        this.propagateChange = fn;
    }

    registerOnTouched(fn: any) { }

    increment() {
        this.count++;
    }

    decrement() {
        this.count--;
    }
}

Usándolo dentro de formularios basados ​​en plantillas

Hay dos formas en Angular 4.x:

  • Formularios basados ​​en plantillas: formularios basados ​​en plantillas (similares a los formularios en Angular 1.x)

  • Formas reactivas-Formas reactivas

Para obtener más información acerca de los formularios basados en plantillas 4.x angular, consulte To-  y basadas en plantillas angular 4.x . A continuación, veremos cómo usarlo:

1. Importe el módulo FormsModule

app.module.ts

import { FormsModule } from '@angular/forms';

@NgModule({
  imports: [BrowserModule, FormsModule],
  ...
})
export class AppModule { }

2. Actualizar AppComponent

2.1 El valor inicial del componente CounterComponent no está establecido

app.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'exe-app',
  template: `
    <form #form="ngForm">
      <exe-counter name="counter" ngModel></exe-counter>
    </form>
    <pre>{
   
   { form.value | json }}</pre>
  `,
})
export class AppComponent { }

Recordatorio amistoso: en el código de muestra anterior, form.value se usa para obtener el valor en el formulario, y json es una canalización integrada de Angular para realizar operaciones de serialización de objetos (implementación interna-JSON.stringify (valor, nulo, 2)). Si desea obtener más información sobre las tuberías angulares, consulte:  tubería angular 2 .

2.2 Establezca el valor inicial del componente CounterComponent-use la sintaxis [ngModel]

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'exe-app',
  template: `
    <form #form="ngForm">
      <exe-counter name="counter" [ngModel]="outerCounterValue"></exe-counter>
    </form>
    <pre>{
   
   { form.value | json }}</pre>
  `,
})
export class AppComponent { 
  outerCounterValue: number = 5;  
}

2.3 Configurar la sintaxis de uso de enlace bidireccional de datos [(ngModel)]

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'exe-app',
  template: `
    <form #form="ngForm">
      <p>outerCounterValue value: {
   
   {outerCounterValue}}</p>
      <exe-counter name="counter" [(ngModel)]="outerCounterValue"></exe-counter>
    </form>
    <pre>{
   
   { form.value | json }}</pre>
  `,
})
export class AppComponent { 
  outerCounterValue: number = 5;  
}

Utilizándolo dentro de formas reactivas

Para obtener más información acerca de las formas 4.x angular reactiva (Model-Driven), consulte To-  angular 4.x formas reactivas . A continuación, veremos cómo usarlo:

1. Importar ReactiveFormsModule

app.module.ts

import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
  imports: [BrowserModule, ReactiveFormsModule],
  ...
})
export class AppModule { }

2. Actualizar AppComponent

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({
  selector: 'exe-app',
  template: `
    <form [formGroup]="form">
      <exe-counter formControlName="counter"></exe-counter>
    </form>
    <pre>{
   
   { form.value | json }}</pre>
  `,
})
export class AppComponent {
  form: FormGroup;

  constructor(private fb: FormBuilder) { }

  ngOnInit() {
    this.form = this.fb.group({
      counter: 5 // 设置初始值
    });
  }
}

Recordatorio amistoso: en el código anterior, eliminamos los atributos ngModel y name en el formulario basado en plantillas, y lo reemplazamos con el atributo formControlName. Además, usamos los group() métodos proporcionados por el objeto FormBuilder para  crear el objeto FromGroup, y luego usamos [formGroup]="form" el método en la plantilla  para lograr el enlace entre el modelo y el elemento DOM. Para obtener más información acerca de las formas reactivas, por favor refiérase a  4.x formas reactivas angular  .

Finalmente, estamos viendo cómo agregar reglas de validación a nuestros controles personalizados.

Agregar validación personalizada

En la  validación de formularios personalizados de Angular 4.x basada en AbstractControl  , presentamos cómo personalizar la validación de formularios. Para nuestro control personalizado, también es muy conveniente agregar una función de verificación personalizada (limitar el rango válido del valor de control: 0 <= valor <= 10). Los ejemplos específicos son los siguientes:

1. Personalizar VALIDADOR

1.1 Definir la función de verificación

export const validateCounterRange: ValidatorFn = (control: AbstractControl): 
  ValidationErrors => {
    return (control.value > 10 || control.value < 0) ?
        { 'rangeError': { current: control.value, max: 10, min: 0 } } : null;
};

1.2 Registrar un validador personalizado

export const EXE_COUNTER_VALIDATOR = {
    provide: NG_VALIDATORS,
    useValue: validateCounterRange,
    multi: true
};

2. Actualizar AppComponent

A continuación, actualizamos el componente AppComponent y mostramos la información de la excepción en la plantilla del componente:

@Component({
  selector: 'exe-app',
  template: `
    <form [formGroup]="form">
      <exe-counter formControlName="counter"></exe-counter>
    </form>
    <p *ngIf="!form.valid">Counter is invalid!</p>
    <pre>{
   
   { form.get('counter').errors | json }}</pre>
  `,
})

El código completo del componente CounterComponent es el siguiente:

counter.component.ts

import { Component, Input, forwardRef } from '@angular/core';
import {
    ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS,
    AbstractControl, ValidatorFn, ValidationErrors, FormControl
} from '@angular/forms';

export const EXE_COUNTER_VALUE_ACCESSOR: any = {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => CounterComponent),
    multi: true
};

export const validateCounterRange: ValidatorFn = (control: AbstractControl): 
  ValidationErrors => {
    return (control.value > 10 || control.value < 0) ?
        { 'rangeError': { current: control.value, max: 10, min: 0 } } : null;
};

export const EXE_COUNTER_VALIDATOR = {
    provide: NG_VALIDATORS,
    useValue: validateCounterRange,
    multi: true
};

@Component({
    selector: 'exe-counter',
    template: `
    <div>
      <p>当前值: {
   
   { count }}</p>
      <button (click)="increment()"> + </button>
      <button (click)="decrement()"> - </button>
    </div>
    `,
    providers: [EXE_COUNTER_VALUE_ACCESSOR, EXE_COUNTER_VALIDATOR]
})
export class CounterComponent implements ControlValueAccessor {
    @Input() _count: number = 0;

    get count() {
        return this._count;
    }

    set count(value: number) {
        this._count = value;
        this.propagateChange(this._count);
    }

    propagateChange = (_: any) => { };

    writeValue(value: any) {
        if (value) {
            this.count = value;
        }
    }

    registerOnChange(fn: any) {
        this.propagateChange = fn;
    }

    registerOnTouched(fn: any) { }

    increment() {
        this.count++;
    }

    decrement() {
        this.count--;
    }
}

Además de configurar un validador personalizado en los Metadatos del componente CounterComponent, también podemos FormGroup establecer las reglas de validación para cada objeto de control (FormControl) al crear el  objeto. El código a ajustar es el siguiente:

counter.component.ts

@Component({
    selector: 'exe-counter',
    ...,
    providers: [EXE_COUNTER_VALUE_ACCESSOR] // 移除自定义EXE_COUNTER_VALIDATOR
})

app.component.ts

import { validateCounterRange } from './couter.component';
...

export class AppComponent {
  ...
  ngOnInit() {
    this.form = this.fb.group({
      counter: [5, validateCounterRange] // 设置validateCounterRange验证器
    });
  }
}

Hemos implementado la función de verificación personalizada, pero la regla de verificación es que el rango válido de datos es fijo (0 <= valor <= 10). De hecho, una mejor manera es permitir a los usuarios configurar de manera flexible el rango válido de datos. A continuación, optimizaremos las funciones existentes para flexibilizar los componentes que desarrollamos.

Haciendo configurable la validación

El uso esperado de nuestro componente CounterComponent personalizado es el siguiente:

<exe-counter
  formControlName="counter"
  counterRangeMax="10"
  counterRangeMin="0">
</exe-counter>

Primero, necesitamos actualizar el componente CounterComponent para incrementar los atributos de entrada counterRangeMax y counterRangeMin:

@Component(...)
class CounterInputComponent implements ControlValueAccessor {
  ...
  @Input() counterRangeMin: number;

  @Input() counterRangeMax: number;
  ...
}

Luego, necesitamos agregar una  createCounterRangeValidator() función de fábrica para crear dinámicamente validateCounterRange() funciones basadas en el valor máximo establecido (maxValue) y el valor mínimo (minValue)  . Los ejemplos específicos son los siguientes:

export function createCounterRangeValidator(maxValue: number, minValue: number) {
    return (control: AbstractControl): ValidationErrors => {
        return (control.value > +maxValue || control.value < +minValue) ?
          { 'rangeError': { current: control.value, max: maxValue, 
               min: minValue }} : null;
    }
}

En el   artículo de instrucciones de verificación personalizadas de Angular 4.x , presentamos cómo personalizar las instrucciones de verificación. Para implementar la función de verificación personalizada del comando, necesitamos implementar la  Validator interfaz:

export interface Validator {
  validate(c: AbstractControl): ValidationErrors|null;
  registerOnValidatorChange?(fn: () => void): void;
}

Además, cuando detectamos  counterRangeMin e  counterRangeMax ingresamos atributos, necesitamos llamar al  createCounterRangeValidator() método, crear dinámicamente la  validateCounterRange() función y luego validate() llamar a la función de verificación en el  método y devolver el valor de retorno de la llamada a la función. Es un poco complicado, echemos un vistazo al código específico de inmediato:

import { Component, Input, OnChanges, SimpleChanges, forwardRef } from '@angular/core';
import {
    ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, Validator,
    AbstractControl, ValidatorFn, ValidationErrors, FormControl
} from '@angular/forms';

...

export const EXE_COUNTER_VALIDATOR = {
    provide: NG_VALIDATORS,
    useExisting: forwardRef(() => CounterComponent),
    multi: true
};

export function createCounterRangeValidator(maxValue: number, minValue: number) {
    return (control: AbstractControl): ValidationErrors => {
        return (control.value > +maxValue || control.value < +minValue) ?
            { 'rangeError': { current: control.value, max: maxValue, min: minValue } } 
              : null;
    }
}

@Component({
    selector: 'exe-counter',
    template: `
    <div>
      <p>当前值: {
   
   { count }}</p>
      <button (click)="increment()"> + </button>
      <button (click)="decrement()"> - </button>
    </div>
    `,
    providers: [EXE_COUNTER_VALUE_ACCESSOR, EXE_COUNTER_VALIDATOR]
})
export class CounterComponent implements ControlValueAccessor, Validator,
    OnChanges {
    ...
    private _validator: ValidatorFn;
    private _onChange: () => void;

    @Input() counterRangeMin: number; // 设置数据有效范围的最大值

    @Input() counterRangeMax: number; // 设置数据有效范围的最小值

    // 监听输入属性变化,调用内部的_createValidator()方法,创建RangeValidator
    ngOnChanges(changes: SimpleChanges): void {
        if ('counterRangeMin' in changes || 'counterRangeMax' in changes) {
            this._createValidator();
        }
    }

    // 动态创建RangeValidator
    private _createValidator(): void {
        this._validator = createCounterRangeValidator(this.counterRangeMax,
           this.counterRangeMin);
    }

    // 执行控件验证
    validate(c: AbstractControl): ValidationErrors | null {
        return this.counterRangeMin == null || this.counterRangeMax == null ? 
            null : this._validator(c);
    }
      
  ...
}

El código anterior es muy largo, desglosémoslo:

Validador de registro

export const EXE_COUNTER_VALIDATOR = {
    provide: NG_VALIDATORS,
    useExisting: forwardRef(() => CounterComponent),
    multi: true
};

@Component({
    selector: 'exe-counter',
    ...,
    providers: [EXE_COUNTER_VALUE_ACCESSOR, EXE_COUNTER_VALIDATOR]
})

Cree la función de fábrica createCounterRangeValidator ()

export function createCounterRangeValidator(maxValue: number, minValue: number) {
    return (control: AbstractControl): ValidationErrors => {
        return (control.value > +maxValue || control.value < +minValue) ?
            { 'rangeError': { current: control.value, max: maxValue, min: minValue } } 
              : null;
    }
}

Implementar la interfaz OnChanges, monitorear los cambios de atributos de entrada para crear un RangeValidator

export class CounterComponent implements ControlValueAccessor, Validator,
    OnChanges {
    ...
    @Input() counterRangeMin: number; // 设置数据有效范围的最大值
    @Input() counterRangeMax: number; // 设置数据有效范围的最小值
    
    // 监听输入属性变化,调用内部的_createValidator()方法,创建RangeValidator
    ngOnChanges(changes: SimpleChanges): void {
        if ('counterRangeMin' in changes || 'counterRangeMax' in changes) {
            this._createValidator();
        }
    }
  ...
}

Llame al método _createValidator () para crear un RangeValidator

export class CounterComponent implements ControlValueAccessor, Validator,
    OnChanges {
    ...
    // 动态创建RangeValidator
    private _createValidator(): void {
        this._validator = createCounterRangeValidator(this.counterRangeMax,
           this.counterRangeMin);
    }
  ...
}

Implementar la interfaz Validator para implementar la función de verificación de control

export class CounterComponent implements ControlValueAccessor, Validator,
    OnChanges {
    ...
    // 执行控件验证
    validate(c: AbstractControl): ValidationErrors | null {
        return this.counterRangeMin == null || this.counterRangeMax == null ? 
            null : this._validator(c);
    }
   ...
}

En este punto, nuestro componente CounterComponent personalizado finalmente se ha desarrollado, y está corto de verificación funcional. Los ejemplos de uso específicos son los siguientes:

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({
  selector: 'exe-app',
  template: `
    <form [formGroup]="form">
      <exe-counter formControlName="counter" 
        counterRangeMin="5" 
        counterRangeMax="8">
      </exe-counter>
    </form>
    <p *ngIf="!form.valid">Counter is invalid!</p>
    <pre>{
   
   { form.get('counter').errors | json }}</pre>
  `,
})
export class AppComponent {
  form: FormGroup;

  constructor(private fb: FormBuilder) { }

  ngOnInit() {
    this.form = this.fb.group({
      counter: 5
    });
  }
}

Una vez que el código anterior se ejecuta correctamente, el resultado que se muestra en la página del navegador es el siguiente:

descripción de la imagen

Recursos de referencia

Supongo que te gusta

Origin blog.csdn.net/u013475983/article/details/103292170
Recomendado
Clasificación