Reaktif Formlar - alanları dokunulmuş olarak işaretleyin


88

Tüm form alanlarını dokundu olarak nasıl işaretleyeceğimi bulmakta güçlük çekiyorum. Asıl sorun, alanlara dokunmazsam ve form göndermeye çalışırsam - görünmeyen doğrulama hatasıdır. Denetleyicimde bu kod parçası için yer tutucum var.
Benim fikrim basit:

  1. kullanıcı gönder düğmesini tıklar
  2. tüm alanlar dokunduğunu işaretler
  3. error formatter yeniden çalıştırır ve doğrulama hatalarını görüntüler

Yeni yöntem uygulamadan gönderirken hataların nasıl gösterileceği konusunda başka bir fikri olan varsa, lütfen bunları paylaşın. Teşekkürler!


Basitleştirilmiş formum:

<form class="form-horizontal" [formGroup]="form" (ngSubmit)="onSubmit(form.value)">
    <input type="text" id="title" class="form-control" formControlName="title">
    <span class="help-block" *ngIf="formErrors.title">{{ formErrors.title }}</span>
    <button>Submit</button>
</form>

Ve denetleyicim:

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

@Component({
  selector   : 'pastebin-root',
  templateUrl: './app.component.html',
  styleUrls  : ['./app.component.css']
})
export class AppComponent implements OnInit {
  form: FormGroup;
  formErrors = {
    'title': ''
  };
  validationMessages = {
    'title': {
      'required': 'Title is required.'
    }
  };

  constructor(private fb: FormBuilder) {
  }

  ngOnInit(): void {
    this.buildForm();
  }

  onSubmit(form: any): void {
    // somehow touch all elements so onValueChanged will generate correct error messages

    this.onValueChanged();
    if (this.form.valid) {
      console.log(form);
    }
  }

  buildForm(): void {
    this.form = this.fb.group({
      'title': ['', Validators.required]
    });
    this.form.valueChanges
      .subscribe(data => this.onValueChanged(data));
  }

  onValueChanged(data?: any) {
    if (!this.form) {
      return;
    }

    const form = this.form;

    for (const field in this.formErrors) {
      if (!this.formErrors.hasOwnProperty(field)) {
        continue;
      }

      // clear previous error message (if any)
      this.formErrors[field] = '';
      const control = form.get(field);
      if (control && control.touched && !control.valid) {
        const messages = this.validationMessages[field];
        for (const key in control.errors) {
          if (!control.errors.hasOwnProperty(key)) {
            continue;
          }
          this.formErrors[field] += messages[key] + ' ';
        }
      }
    }
  }
}

Yanıtlar:


147

Aşağıdaki işlev, bir form grubundaki kontroller aracılığıyla tekrar eder ve onlara nazikçe dokunur. Denetimler alanı bir nesne olduğundan, kod, form grubunun denetim alanında Object.values ​​() öğesini çağırır.

  /**
   * Marks all controls in a form group as touched
   * @param formGroup - The form group to touch
   */
  private markFormGroupTouched(formGroup: FormGroup) {
    (<any>Object).values(formGroup.controls).forEach(control => {
      control.markAsTouched();

      if (control.controls) {
        this.markFormGroupTouched(control);
      }
    });
  }

19
Bu ne yazık ki Internet Explorer değil iş :( basitçe değiştirir (<any>Object).values(formGroup.controls)için Object.keys(formGroup.controls).map(x => formGroup.controls[x])(den stackoverflow.com/questions/42830257/... )
moi_meme

1
Bu FormGroup ve FormControl kullanmam ve kullanıcıya gerekli bir alana dokunmadıklarını nasıl göstereceğimi merak etmem için çok yardımcı oldu. Teşekkür ederim.
NAMS

@NAMS sorun değil!
Yardımcı

4
+1 Özyinelemeli kısımda sadece bir küçük sorun. Zaten controlsişlevin başlangıcında if (control.controls) { markFormGroupTouched(control); }
yineliyorsunuz

3
touchedsadece girdinin bir kez bulanık olduğu anlamına gelir. Hataların görünmesi için, updateValueAndValidity()kontrollerimi de çağırmam gerekiyordu .
adamdport

109

Gönderen Açısal 8/9 basitçe kullanabilirsiniz

this.form.markAllAsTouched();

Bir kontrolü ve alt kontrollerini dokunduğunuzda işaretlemek için.

AbstractControl belgesi


2
Angular 8 kullananlar için kabul edilen cevap bu olmalıdır.
Jacob Roberts

1
Bu daha basit ve temiz bir çözümdür.
HDJEMAI

1
bu, açısal 8 ve üstü için önerilen çözümdür, harika!
Duc Nguyen

1
Bu, bazı kontroller için çalışmıyor gibi görünüyorsa, büyük olasılıkla bu FormGroup içinde değildirler.
Numen

12

@ Masterwork'ün cevabıyla ilgili olarak. Bu çözümü denedim, ancak işlev bir FormGroup içinde yinelemeli olarak kazmaya çalıştığında bir hata aldım, çünkü bu satırda FormGroup yerine bir FormControl argümanı geçiyor:

control.controls.forEach(c => this.markFormGroupTouched(c));

İşte benim çözümüm

markFormGroupTouched(formGroup: FormGroup) {
 (<any>Object).values(formGroup.controls).forEach(control => {
   if (control.controls) { // control is a FormGroup
     markFormGroupTouched(control);
   } else { // control is a FormControl
     control.markAsTouched();
   }
 });
}


8

Form kontrolleri arasında dolaşmak ve bunları dokunduğunuz şekilde işaretlemek de işe yarayacaktır:

for(let i in this.form.controls)
    this.form.controls[i].markAsTouched();

1
Teşekkürler dostum, çözümünüz oldukça iyi çünkü tslint şikayetleri şu: for (const i in this.form.controls) {if (this.form.controls [i]) {this.form.controls [i ] .markAsTouched (); }}
Avram Virgil

1
Bu, sizin formGroupbaşka formGroups
adamdport

3

Bu benim çözümüm

      static markFormGroupTouched (FormControls: { [key: string]: AbstractControl } | AbstractControl[]): void {
        const markFormGroupTouchedRecursive = (controls: { [key: string]: AbstractControl } | AbstractControl[]): void => {
          _.forOwn(controls, (c, controlKey) => {
            if (c instanceof FormGroup || c instanceof FormArray) {
              markFormGroupTouchedRecursive(c.controls);
            } else {
              c.markAsTouched();
            }
          });
        };
        markFormGroupTouchedRecursive(FormControls);
      }

2

Bu sorunu yaşadım, ancak şimdiye kadar bulduğum herhangi bir Açısal öğreticide olmamasına rağmen, bunu yapmanın "doğru" yolunu buldum.

HTML'nizde, formetiketin üzerine #myVariable='ngForm', Reaktif Form örneklerinin kullandığına ek olarak, Şablona Dayalı Form örneklerinin kullandığı Şablon Referans Değişkenini ('hashtag' değişkeni) ekleyin :

<form [formGroup]="myFormGroup" #myForm="ngForm" (ngSubmit)="submit()">

Artık myForm.submittedşablonda aşağıdakilerin yerine (veya ek olarak) kullanabileceğiniz erişime sahipsiniz myFormGroup.controls.X.touched:

<div *ngIf="myForm.submitted" class="text-error"> <span *ngIf="myFormGroup.controls.myFieldX.errors?.badDate">invalid date format</span> <span *ngIf="myFormGroup.controls.myFieldX.errors?.isPastDate">date cannot be in the past.</span> </div>

Bunun myForm.form === myFormGroupdoğru olduğunu bilin ... ="ngForm"kısmı unutmadığınız sürece . Tek #myFormbaşına kullanırsanız , çalışmayacaktır çünkü var, bu öğeyi süren Direktif yerine HtmlElement'e ayarlanacaktır.

Bilin ki myFormGroupReaktif Formlar öğreticiler başına komponente typescript kodunda görülebilir, ancak myFormsizin gibi bir yöntem çağrısı içeri geçmek sürece değildir submit(myForm)için submit(myForm: NgForm): void {...}. ( NgFormDaktilo yazısında başlık büyük harflerinde, HTML'de ise deve büyüklüğünde bildirim bulunur .)


1
onSubmit(form: any): void {
  if (!this.form) {
    this.form.markAsTouched();
    // this.form.markAsDirty(); <-- this can be useful 
  }
}

Sadece denedim ve bir şekilde çocuk formundaki öğelere dokunmuyor. Tüm alt öğeleri manuel olarak işaretleyen döngü yazmak zorunda kaldı. markAsTouched()Alt öğelere neden dokunmadığına dair herhangi bir fikrin var mı?
Giedrius Kiršys

Hangi açısal versiyonları kullanıyorsunuz?
Vlado Tesanovic

Açısal sürüm 2.1.0
Giedrius Kiršys

1
Görünüşe göre neden markAsTouched()alt öğeleri işaretlemediğimi buldum - github.com/angular/angular/issues/11774 . TL; DR: Bu bir hata değil.
Giedrius Kiršys

1
Evet, şimdi hatırladım. Form geçerli değilse gönder düğmesini devre dışı bırakabilirsiniz, <button [disable] = "! This.form"> Gönder </button>
Vlado Tesanovic

1

Ben de aynı problemle karşılaştım, ancak bileşenlerimi bunu işleyen bir kodla "kirletmek" istemiyorum. Özellikle buna birçok biçimde ihtiyacım olduğu ve kodu çeşitli durumlarda tekrarlamak istemediğim için.

Böylece bir yönerge oluşturdum (şimdiye kadar gönderilen yanıtları kullanarak). Yönerge, NgForm'un- onSubmitYöntemini dekore eder : Eğer form geçersizse, tüm alanları dokunulmuş olarak işaretler ve gönderimi iptal eder. Aksi takdirde, olağan onSubmit-Method normal olarak yürütülür.

import {Directive, Host} from '@angular/core';
import {NgForm} from '@angular/forms';

@Directive({
    selector: '[appValidateOnSubmit]'
})
export class ValidateOnSubmitDirective {

    constructor(@Host() form: NgForm) {
        const oldSubmit = form.onSubmit;

        form.onSubmit = function (): boolean {
            if (form.invalid) {
                const controls = form.controls;
                Object.keys(controls).forEach(controlName => controls[controlName].markAsTouched());
                return false;
            }
            return oldSubmit.apply(form, arguments);
        };
    }
}

Kullanım:

<form (ngSubmit)="submit()" appValidateOnSubmit>
    <!-- ... form controls ... -->
</form>

1

Aslında kullandığım kod bu.

validateAllFormFields(formGroup: any) {
    // This code also works in IE 11
    Object.keys(formGroup.controls).forEach(field => {
        const control = formGroup.get(field);

        if (control instanceof FormControl) {
            control.markAsTouched({ onlySelf: true });
        } else if (control instanceof FormGroup) {               
            this.validateAllFormFields(control);
        } else if (control instanceof FormArray) {  
            this.validateAllFormFields(control);
        }
    });
}    


1

Bu kod benim için çalışıyor:

markAsRequired(formGroup: FormGroup) {
  if (Reflect.getOwnPropertyDescriptor(formGroup, 'controls')) {
    (<any>Object).values(formGroup.controls).forEach(control => {
      if (control instanceof FormGroup) {
        // FormGroup
        markAsRequired(control);
      }
      // FormControl
      control.markAsTouched();
    });
  }
}

1

Özyinelemesiz bir çözüm

Performans konusunda endişelenenler için, her seviyedeki tüm kontrolleri yinelemesine rağmen yinelemeyi kullanmayan bir çözüm buldum.

 /**
  * Iterates over a FormGroup or FormArray and mark all controls as
  * touched, including its children.
  *
  * @param {(FormGroup | FormArray)} rootControl - Root form
  * group or form array
  * @param {boolean} [visitChildren=true] - Specify whether it should
  * iterate over nested controls
  */
  public markControlsAsTouched(rootControl: FormGroup | FormArray,
    visitChildren: boolean = true) {

    let stack: (FormGroup | FormArray)[] = [];

    // Stack the root FormGroup or FormArray
    if (rootControl &&
      (rootControl instanceof FormGroup || rootControl instanceof FormArray)) {
      stack.push(rootControl);
    }

    while (stack.length > 0) {
      let currentControl = stack.pop();
      (<any>Object).values(currentControl.controls).forEach((control) => {
        // If there are nested forms or formArrays, stack them to visit later
        if (visitChildren &&
            (control instanceof FormGroup || control instanceof FormArray)
           ) {
           stack.push(control);
        } else {
           control.markAsTouched();
        }
      });
    }
  }

Bu çözüm hem FormGroup hem de FormArray'den çalışır.

Onunla burada oynayabilirsiniz: dokunulmuş açısal işaretleme


@VladimirPrudnikov Sorun, bir işleve yinelemeli çağrı yaparken genellikle daha fazla ek yükün ilişkili olmasıdır. Bu nedenle, CPU çağrı yığınını işlemek için daha fazla zaman harcayacaktır. Döngüleri kullanırken CPU çoğu zaman algoritmanın kendisini gerçekleştirmek için harcar. Özyinelemenin avantajı, kodun genellikle daha okunabilir olmasıdır. Dolayısıyla, performans bir sorun değilse, özyinelemeye bağlı kalabileceğinizi söyleyebilirim.
Arthur Silva

"Erken optimizasyon, tüm kötülüklerin köküdür."
Dem Pilafian

@DemPilafian Alıntıya katılıyorum. Ancak burada geçerli değildir, çünkü birisi bu konu başlığına gelirse, ücretsiz olarak optimize edilmiş bir çözüm elde edebilir (bunun için zaman harcanmaz). Ve btw, benim durumumda onu optimize etmek için gerçekten nedenlerim vardı =)
Arthur Silva

1

@masterwork'e göre

açısal sürüm 8 için daktilo kodu

private markFormGroupTouched(formGroup: FormGroup) {
    (Object as any).values(formGroup.controls).forEach(control => {
      control.markAsTouched();
      if (control.controls) {
        this.markFormGroupTouched(control);
      }
    });   }

0

İşte böyle yapıyorum. Gönder düğmesine basılana (veya forma dokunulana) kadar hata alanlarının gösterilmesini istemiyorum.

import {FormBuilder, FormGroup, Validators} from "@angular/forms";

import {OnInit} from "@angular/core";

export class MyFormComponent implements OnInit {
  doValidation = false;
  form: FormGroup;


  constructor(fb: FormBuilder) {
    this.form = fb.group({
      title: ["", Validators.required]
    });

  }

  ngOnInit() {

  }
  clickSubmitForm() {
    this.doValidation = true;
    if (this.form.valid) {
      console.log(this.form.value);
    };
  }
}

<form class="form-horizontal" [formGroup]="form" >
  <input type="text" class="form-control" formControlName="title">
  <div *ngIf="form.get('title').hasError('required') && doValidation" class="alert alert-danger">
            title is required
        </div>
  <button (click)="clickSubmitForm()">Submit</button>
</form>


Bu, yeni doğrulama kuralları eklerken zamanla ağırlaşabilir gibi görünüyor. Ama anladım.
Giedrius Kiršys

0

OP'nin hayal kırıklığını tamamen anlıyorum. Ben aşağıdakileri kullanıyorum:

Fayda işlevi :

/**
 * Determines if the given form is valid by touching its controls 
 * and updating their validity.
 * @param formGroup the container of the controls to be checked
 * @returns {boolean} whether or not the form was invalid.
 */
export function formValid(formGroup: FormGroup): boolean {
  return !Object.keys(formGroup.controls)
    .map(controlName => formGroup.controls[controlName])
    .filter(control => {
      control.markAsTouched();
      control.updateValueAndValidity();
      return !control.valid;
    }).length;
}

Kullanım :

onSubmit() {
  if (!formValid(this.formGroup)) {
    return;
  }
  // ... TODO: logic if form is valid.
}

Bu işlevin henüz iç içe geçmiş kontrolleri karşılamadığını unutmayın.


0

Bu mücevheri görün . Şimdiye kadar gördüğüm en zarif çözüm.

Tam kod

import { Injectable } from '@angular/core';
import { FormGroup } from '@angular/forms';

const TOUCHED = 'markAsTouched';
const UNTOUCHED = 'markAsUntouched';
const DIRTY = 'markAsDirty';
const PENDING = 'markAsPending';
const PRISTINE = 'markAsPristine';

const FORM_CONTROL_STATES: Array<string> = [TOUCHED, UNTOUCHED, DIRTY, PENDING, PRISTINE];

@Injectable({
  providedIn: 'root'
})
export class FormStateService {

  markAs (form: FormGroup, state: string): FormGroup {
    if (FORM_CONTROL_STATES.indexOf(state) === -1) {
      return form;
    }

    const controls: Array<string> = Object.keys(form.controls);

    for (const control of controls) {
      form.controls[control][state]();
    }

    return form;
  }

  markAsTouched (form: FormGroup): FormGroup {
    return this.markAs(form, TOUCHED);
  }

  markAsUntouched (form: FormGroup): FormGroup {
    return this.markAs(form, UNTOUCHED);
  }

  markAsDirty (form: FormGroup): FormGroup {
    return this.markAs(form, DIRTY);
  }

  markAsPending (form: FormGroup): FormGroup {
    return this.markAs(form, PENDING);
  }

  markAsPristine (form: FormGroup): FormGroup {
    return this.markAs(form, PRISTINE);
  }
}

0
    /**
    * Marks as a touched
    * @param { FormGroup } formGroup
    *
    * @return {void}
    */
    markFormGroupTouched(formGroup: FormGroup) {
        Object.values(formGroup.controls).forEach((control: any) => {

            if (control instanceof FormControl) {
                control.markAsTouched();
                control.updateValueAndValidity();

            } else if (control instanceof FormGroup) {
                this.markFormGroupTouched(control);
            }
        });
    }

0

Görünüm:

<button (click)="Submit(yourFormGroup)">Submit</button>   

API

Submit(form: any) {
  if (form.status === 'INVALID') {
      for (let inner in details.controls) {
           details.get(inner).markAsTouched();
       }
       return false; 
     } 
     // as it return false it breaks js execution and return 

0

Sunulan cevaplarda bazı değişiklikler içeren bir versiyon yaptım, angular'ın 8. versiyonundan daha eski versiyonları kullananlar için, faydalı olanlarla paylaşmak istiyorum.

Fayda işlevi:

import {FormControl, FormGroup} from "@angular/forms";

function getAllControls(formGroup: FormGroup): FormControl[] {
  const controls: FormControl[] = [];
  (<any>Object).values(formGroup.controls).forEach(control => {
    if (control.controls) { // control is a FormGroup
      const allControls = getAllControls(control);
      controls.push(...allControls);
    } else { // control is a FormControl
      controls.push(control);
    }
  });
  return controls;
}

export function isValidForm(formGroup: FormGroup): boolean {
  return getAllControls(formGroup)
    .filter(control => {
      control.markAsTouched();
      return !control.valid;
    }).length === 0;
}

Kullanım:

onSubmit() {
 if (this.isValidForm()) {
   // ... TODO: logic if form is valid
 }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.