validate invalid form custom angular

invalid - ¿Cómo puedo resolver el mismo problema en Angular, que los mensajes ng se resuelven en AngularJS?



validate required field angularjs (3)

Junto a mi otra respuesta, también podría usar una biblioteca que he creado llamada angular-reactive-validation que se puede encontrar here .

Usted indica que es importante para usted mostrar a lo sumo un mensaje de validación por control en un momento dado. Esta biblioteca soporta ese comportamiento. También reduce la cantidad de HTML que necesita escribir para mostrar los mensajes de validación, ya que la declaración de los mensajes de validación se mueve al Component donde declara sus FormControls . Otra característica útil es tener valores de validación dinámica al pasar una función al Validator .

A continuación, proporcioné un formulario de ejemplo y el modelo que se encuentra detrás para darle una idea del uso básico.

<form [formGroup]="form"> <div formGroupName="name"> <label>First name: <input formControlName="firstName"> </label> <label>Middle name: <input formControlName="middleName"> </label> <label>Last name: <input formControlName="lastName"> </label> <br /> <arv-validation-messages [for]="[''firstName'', ''middleName'', ''lastName'']"> </arv-validation-messages> </div> <label>Age: <input type="number" formControlName="age"> </label> <arv-validation-messages for="age"></arv-validation-messages> <br /> <input type="submit" /> </form>

import { Validators } from ''angular-reactive-validation''; ... form = this.fb.group({ name: this.fb.group({ firstName: ['''', [Validators.required(''A first name is required''), Validators.minLength(1, minLength => `The minimum length is ${minLength}`), Validators.maxLength(50, maxLength => `Maximum length is ${maxLength}`)]], middleName: ['''', [Validators.maxLength(50, maxLength => `Maximum length is ${maxLength}`)]], lastName: ['''', [Validators.required(''A last name is required''), Validators.maxLength(50, maxLength => `Maximum length is ${maxLength}`)]] }), age: [null, [ Validators.required(''An age is required''), Validators.min(0, ''You can/'t be less than zero years old.''), Validators.max(150, max => `Can''t be more than ${max}`) ]] });

En AngularJS había una directiva de formulario llamada ng-messages que nos ayudó a hacerlo para que no se mostraran todos los errores de formulario al mismo tiempo. Entonces, por ejemplo, si una entrada tiene 3 errores: requerido, minlength, maxlength. Entonces, solo se muestran las presentaciones requeridas, una vez que sea válido es válido, luego aparece minlength. Sin ng-messages tendríamos que hacer una lógica realmente compleja y fea para mostrar solo lo necesario y no el resto, teniendo en cuenta que los errores solo deben mostrarse si el control de formulario también está sucio o no es válido.

En AngularJS esto sería algo como:

<div ng-messages="form.username.$error" ng-if="form.username.$touched || form.username.$dirty"> <div ng-message="required">Please enter a username.</div> <div ng-message="minlength">Username must be at least 3 characters.</div> <div ng-message="maxlength">Username can''t exceed 30 characters.</div> </div>

¿Cómo podemos lograr esto en Angular de una manera elegante?


Sobre la base del código que proporcionó @David Walschots, noté que allí tenemos 2 problemas (en mi caso).

  • no se validó al enviar, pero no quería desactivar el botón de envío
  • no se validó en desenfoque, solo aparecería el color de error, pero no mensajes.

Entonces, después de intentarlo por un tiempo, encontré una solución que también funcionaría en los casos descritos anteriormente.

Para esto necesitaba agregar un oyente adicional para el evento de desenfoque y asegurarme de que el formulario se emitiría en el envío (sin crear el código de la placa de la caldera) .

El oyente extra

El FormControl inyectado no está directamente vinculado al campo de entrada y no tiene un evento de desenfoque conectado. Así que necesitamos encontrar el elemento de entrada html que está conectado a él. Para esto podemos usar el Renderer2 provisto por Angular, pero primero necesitamos encontrar el nombre del control para poder crear un selector de CSS:

/** * Tries to find the name of the given control * Since `Angular 4.0.4` the `FormControl` can have access to it''s parent * @param {FormControl} control - The control of which the name should be determined * @returns {string | null} The name of the control or null if no control was found */ private static getControlName(control: FormControl): string | null { const formGroup = control.parent.controls; return Object.keys(formGroup).find(name => control === formGroup[name]) || null; }

Después de esto podemos crear un selector de CSS y encontrar el elemento en la página:

if (this.control.updateOn === ValMessagesComponent.UPDATE_ON_BLUR) { const controlName = ValMessagesComponent.getControlName(this.control); const input = this.renderer.selectRootElement(''input[formControlName='' + controlName + '']''); }

Ahora tenemos el elemento HTML al que está vinculado FormControl , así que podemos agregar el evento de desenfoque y hacer cosas con él:

this.inputSubscription = this.renderer.listen(input, ''blur'', () => { // Blur happened. Let''s validate! })

Cuando incorporamos este código en la respuesta de @David Walschots, obtenemos el siguiente código:

@Component({ selector: ''val-messages'', template: ''<ng-content></ng-content>'' }) export class ValMessagesComponent implements OnInit, OnDestroy { /** * The form control on which the messages should be shown * @type {FormControl} */ @Input() private control: FormControl; /** * Whether or not the form should be validated on submit * @type {boolean} * @default */ @Input() private onSubmit: boolean = true; /** * All the children directives that are defined within this component of type `sh-message` * These children hold the `when` and the `message` that should be shown * @type {ValMessageComponent} */ @ContentChildren(ValMessageComponent) private messageComponents: QueryList<ValMessageComponent>; /** * All subscriptions that are used to monitor the status of the FormControl * @see control * @type {Subscription[]} */ private controlSubscriptions: Subscription[] = []; /** * A listener for a change on the input field to which the formControl is connected * @type {() => void} */ private inputSubscription: () => void; /** * The key that indicates that the model is updated on blur * @type {string} * @default */ private static readonly UPDATE_ON_BLUR = ''blur''; constructor(private renderer: Renderer2) { } public ngOnInit(): void { this.controlSubscriptions.push(this.control.valueChanges.subscribe(() => { this.hideAllMessages(); this.matchAndShowMessage(this.control.errors); })); this.controlSubscriptions.push(this.control.statusChanges.subscribe(() => { this.hideAllMessages(); this.matchAndShowMessage(this.control.errors); })); if (this.control.updateOn === ValMessagesComponent.UPDATE_ON_BLUR) { const controlName = ValMessagesComponent.getControlName(this.control); const input = this.renderer.selectRootElement(''input[formControlName='' + controlName + '']''); this.inputSubscription = this.renderer.listen(input, ''blur'', () => { this.hideAllMessages(); this.matchAndShowMessage(this.control.errors); }) } } public ngOnDestroy(): void { if (this.inputSubscription) { this.inputSubscription(); } for (const subscription of this.controlSubscriptions) { subscription.unsubscribe(); } } /** * Checks if the model is invalid and if it is, finds and shows the corresponding error message * @param {ValidationErrors} errors - Any errors that are thrown on the model */ private matchAndShowMessage(errors: ValidationErrors): void { if (errors) { const messageComponent = this.messageComponents.find(messageComponent => { return messageComponent.shouldShowError(Object.keys(errors)); }); if (messageComponent) { messageComponent.showMessage(); } } } /** * Hides all the messages on the model */ private hideAllMessages(): void { this.messageComponents.forEach(messageComponent => messageComponent.hideMessage()); } /** * Tries to find the name of the given control * Since `Angular 4.0.4` the `FormControl` can have access to it''s parent * @param {FormControl} control - The control of which the name should be determined * @returns {string | null} The name of the control or null if no control was found */ private static getControlName(control: FormControl): string | null { const formGroup = control.parent.controls; return Object.keys(formGroup).find(name => control === formGroup[name]) || null; } }


Vea mi otra respuesta para una biblioteca que podría usar para este propósito. El resto de esta respuesta va a hacer tus propios componentes.

A continuación, proporciono un ejemplo (no lo compilé ni lo ejecuté, pero debería darle suficiente información para comenzar). La lógica para solo mostrar mensajes cuando se toca, sucio, etc. se puede agregar a esto fácilmente.

Uso

<validation-messages [for]="control"> <validation-message name="required">This field is required</validation-message> </validation-messages>

Implementación

import { Component, OnInit, ContentChildren, QueryList, Input, OnDestroy } from ''@angular/core''; import { FormControl } from ''@angular/forms''; import { Subscription } from ''rxjs''; @Component({ selector: ''validation-messages'', template: ''<ng-content></ng-content>'' }) export class ValidationMessagesComponent implements OnInit, OnDestroy { @Input() for: FormControl; @ContentChildren(ValidationMessageComponent) messageComponents: QueryList<ValidationMessageComponent>; private statusChangesSubscription: Subscription; ngOnInit() { this.statusChangesSubscription = this.for.statusChanges.subscribe(x => { this.messageComponents.forEach(messageComponent => messageComponent.show = false); if (this.for.invalid) { let firstErrorMessageComponent = this.messageComponents.find(messageComponent => { return messageComponent.showsErrorIncludedIn(Object.keys(this.for.errors)); }); firstErrorMessageComponent.show = true; } }); } ngOnDestroy() { this.statusChangesSubscription.unsubscribe(); } } @Component({ selector: ''validation-message'', template: ''<div *ngIf="show"><ng-content></ng-content></div>'' }) export class ValidationMessageComponent { @Input() name: string; show: boolean = false; showsErrorIncludedIn(errors: string[]): boolean { return errors.some(error => error === this.name); } }