प्रतिक्रियाशील रूप - छुआ के रूप में फ़ील्ड चिह्नित करें


88

मुझे यह पता लगाने में परेशानी हो रही है कि छुआ के रूप में सभी फॉर्म के फ़ील्ड को कैसे चिह्नित किया जाए। मुख्य समस्या यह है कि अगर मैं फ़ील्ड्स को नहीं छूता हूं और फॉर्म नहीं जमा करने का प्रयास करता हूं - तो दिखाया नहीं गया है। मेरे पास मेरे नियंत्रक में उस टुकड़े का प्लेसहोल्डर है।
मेरा विचार सरल है:

  1. उपयोगकर्ता क्लिक बटन सबमिट करें
  2. छुआ के रूप में सभी क्षेत्रों के निशान
  3. त्रुटि फॉर्मूला पुनर्मिलन और सत्यापन त्रुटियों को प्रदर्शित करता है

यदि किसी के पास नई पद्धति लागू किए बिना सबमिट करने में त्रुटि दिखाने का कोई अन्य विचार है - तो कृपया उन्हें साझा करें। धन्यवाद!


मेरा सरलीकृत रूप:

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

और मेरे नियंत्रक:

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] + ' ';
        }
      }
    }
  }
}

जवाबों:


147

निम्न फ़ंक्शन एक प्रपत्र समूह में नियंत्रण के माध्यम से पुनरावृत्ति करता है और धीरे से उन्हें छूता है। नियंत्रण फ़ील्ड ऑब्जेक्ट है, क्योंकि प्रपत्र समूह के नियंत्रण फ़ील्ड पर कोड कॉल Object.values ​​()।

  /**
   * 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
इस दुर्भाग्य से इंटरनेट एक्सप्लोरर में काम नहीं :( बस में बदल जाती है (<any>Object).values(formGroup.controls)के लिए Object.keys(formGroup.controls).map(x => formGroup.controls[x])(से stackoverflow.com/questions/42830257/... )
moi_meme

1
यह FormGroup और FormControl का उपयोग करके मेरे लिए एक बड़ी मदद थी और यह सोचकर कि उपयोगकर्ता को यह दिखाने के लिए कि वे एक आवश्यक फ़ील्ड को नहीं छूते हैं। धन्यवाद।
NAMS

@ कोई समस्या नहीं! मुझे खुशी है कि इसने मदद की:]
मास्टरवोक

4
+1 पुनरावर्ती भाग में सिर्फ एक मामूली समस्या। आप controlsसमारोह की शुरुआत में पहले से ही इसे देख रहे हैं , इसलिए इसके बजाय निम्नलिखित होना चाहिए:if (control.controls) { markFormGroupTouched(control); }
zurfyx

3
touchedबस इसका मतलब है कि इनपुट एक बार धुंधला हो गया था। त्रुटियों को प्रकट करने के लिए, मुझे updateValueAndValidity()अपने नियंत्रणों पर भी कॉल करना था ।
adamdport

108

से कोणीय 8/9 आप बस का उपयोग कर सकते

this.form.markAllAsTouched();

एक नियंत्रण को चिह्नित करने के लिए और यह स्पर्श के रूप में वंशज नियंत्रण है।

AbstractControl डॉक्टर


2
यह Angular 8 का उपयोग करने वालों के लिए स्वीकृत उत्तर होना चाहिए।
याकूब रॉबर्ट्स

1
यह एक सरल और साफ समाधान है।
HDJEMAI

1
यह कोणीय 8 और उच्चतर, महान के लिए अनुशंसित समाधान है!
डुक गुयेन

1
यदि ऐसा लगता है कि कुछ नियंत्रणों के लिए काम नहीं कर रहे हैं, तो वे शायद उस फॉर्मग्रुप में नहीं हैं।
नौमेनन

12

@ मास्टरवर्क के जवाब के बारे में। मैंने उस समाधान की कोशिश की, लेकिन मुझे एक त्रुटि मिली जब फ़ंक्शन ने फॉर्मग्रुप के अंदर, पुनरावर्ती रूप से खुदाई करने की कोशिश की, क्योंकि इस लाइन पर एक फॉर्मग्रुप के बजाय एक फॉर्मकंट्रोल तर्क पारित हो रहा है:

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

यहाँ मेरा समाधान है

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();
   }
 });
}

10

कोणीय v8 से, आपके पास यह अंतर्निहित है जिसकी मदद से markAllAsTouched विधि ।

एक उदाहरण के रूप में, आप इसे पसंद कर सकते हैं

form.markAllAsTouched();

आधिकारिक डॉक देखें: https://angular.io/api/forms/AbstractControl#markallastouched


8

प्रपत्र नियंत्रण के माध्यम से लूपिंग और उन्हें छुआ हुआ के रूप में चिह्नित करना भी काम करेगा:

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

1
धन्यवाद दोस्त आपका समाधान काफी अच्छा है केवल एक चीज है जो मैं जोड़ूंगा क्योंकि tslint शिकायत यह है: for (const i in this.form.controls) {if (this.form.controls [i]) {this.form .controls [i ] .markAsTouched (); }}
अवराम वर्जिल

1
यह काम नहीं करता है अगर आपके formGroupअन्य formGroups
adamdport

3

यह मेरा समाधान है

      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

मेरे पास यह मुद्दा था, लेकिन ऐसा करने का "सही" तरीका मिला, इसके बावजूद कि मैंने कभी भी किसी भी कोणीय ट्यूटोरियल में नहीं पाया।

अपने HTML में, formटैग पर, उसी टेम्पलेट संदर्भ वैरिएबल #myVariable='ngForm'('हैशटैग' चर) को जोड़ें, जो टेम्पलेट-प्रेरित फॉर्म उदाहरणों का उपयोग करता है, इसके अलावा रिएक्टिव फॉर्म उदाहरणों का उपयोग करते हैं:

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

अब आपके पास myForm.submittedउस टेम्पलेट का उपयोग है जिसे आप (या इसके अलावा) के बजाय उपयोग कर सकते हैं 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>

पता है कि myForm.form === myFormGroupयह सच है ... जब तक आप ="ngForm"भाग को नहीं भूलते हैं । यदि आप #myFormअकेले उपयोग करते हैं, तो यह काम नहीं करेगा क्योंकि उस तत्व को निर्देश ड्राइविंग के बजाय var को HtmlElement पर सेट किया जाएगा।

पता है कि myFormGroupआपके घटक टाइपस्क्रिप्ट कोड में प्रतिक्रियाशील प्रपत्र ट्यूटोरियल के अनुसार दिखाई दे रहा है , लेकिन myFormजब तक आप इसे एक विधि कॉल के माध्यम से पारित नहीं करते हैं, जैसे submit(myForm)कि submit(myForm: NgForm): void {...}। (सूचना NgFormHTML में टाइटल कैप में लेकिन ऊंट मामले में है।)


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

बस कोशिश की है और किसी भी तरह यह बाल फार्म तत्वों को नहीं छूता है। लूप लिखना था जो सभी बाल तत्वों को मैन्युअल रूप से चिह्नित करता है। क्या आपके पास कोई सुराग है कि markAsTouched()बाल तत्वों को क्यों नहीं छूता है?
गिएड्रीस किर्किस

आप किस कोणीय संस्करण का उपयोग कर रहे हैं?
व्लादो टेसानोविक

कोणीय संस्करण 2.1.0
Giedrius Kiršys

1
ऐसा लगता है कि मैंने पाया कि markAsTouched()बाल तत्व क्यों नहीं चिह्नित किए गए हैं - github.com/angular/angular/issues/11774 । TL; DR: यह बग नहीं है।
गिड्रिएस किर्किस 15

1
हां, मुझे अब याद है। यदि फॉर्म वैध नहीं है तो आप सबमिट बटन को अक्षम कर सकते हैं, <बटन [अक्षम] = "! इस" "" सबमिट करें </ बटन>
व्लादो टेसनोविक

1

मैं उसी समस्या में भाग गया, लेकिन मैं अपने घटकों को कोड के साथ "प्रदूषित" नहीं करना चाहता जो इसे संभालते हैं। खासकर जब से मुझे कई रूपों में इसकी आवश्यकता है और मैं विभिन्न अवसरों पर कोड को दोहराना नहीं चाहता हूं।

इस प्रकार मैंने एक निर्देश बनाया (अब तक पोस्ट किए गए उत्तरों का उपयोग करके)। निर्देशन NgForm's onSubmit-Method को सजाता है: यदि फॉर्म अमान्य है तो यह सभी फ़ील्ड्स को छुआ हुआ और प्रस्तुत करने को रोकता है। अन्यथा सामान्य रूप से onSubmit-Method सामान्य रूप से निष्पादित होता है।

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);
        };
    }
}

उपयोग:

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

1

यह वह कोड है जो मैं वास्तव में उपयोग कर रहा हूं।

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

यह कोड मेरे लिए काम करता है:

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

पुनरावृत्ति के बिना एक समाधान

प्रदर्शन के बारे में चिंतित लोगों के लिए, मैं एक समाधान के साथ आया हूं जो पुनरावृत्ति का उपयोग नहीं करता है, हालांकि यह अभी भी सभी स्तरों पर सभी नियंत्रणों पर पुनरावृत्त करता है।

 /**
  * 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();
        }
      });
    }
  }

यह समाधान फॉर्मग्रुप और फॉर्मअरे दोनों का निर्माण करता है।

आप इसके साथ यहां खेल सकते हैं: कोणीय-चिह्न-जैसा-स्पर्श किया गया


@VladimirPrudnikov समस्या यह है कि एक समारोह में पुनरावर्ती कॉल करते समय आमतौर पर अधिक उपरि जुड़े होते हैं। उसके कारण, CPU कॉल स्टैक को संभालने में अधिक समय बिताएगा। जब लूप्स का उपयोग करते हैं तो सीपीयू अधिकांश समय एल्गोरिथ्म का प्रदर्शन करने में खर्च करेगा। पुनरावृत्ति का लाभ यह है कि कोड आमतौर पर अधिक पठनीय होता है। इसलिए, यदि प्रदर्शन कोई समस्या नहीं है, तो मैं कहूंगा कि आप पुनरावृत्ति के साथ रह सकते हैं।
आर्थर सिल्वा

"सभी बुराईयो की जड़ समयपूर्व इष्टतमीकरण है।"
डेम पिलाफियन

@DemPilafian मैं उद्धरण से सहमत हूं। हालाँकि, यह यहाँ लागू नहीं होता है, क्योंकि यदि कोई इस धागे तक आता है तो वे मुफ्त में एक अनुकूलित समाधान प्राप्त कर सकेंगे (इस पर कोई समय नहीं खर्च किया जाएगा)। और, btw, मेरे मामले में मेरे पास वास्तव में इसे अनुकूलित करने के लिए कारण थे =)
आर्थर सिल्वा

1

@ मास्टरवर्क के अनुसार

टाइपिंग कोड कोणीय संस्करण 8 के लिए

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

0

यहाँ है कि मैं यह कैसे करते हैं। जब तक सबमिट बटन दबाया नहीं जाता (या फॉर्म को छुआ गया) तब तक मैं त्रुटि फ़ील्ड नहीं दिखाना चाहता।

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>


ऐसा लगता है कि नए सत्यापन नियमों को जोड़ने पर यह समय के साथ भारी हो सकता है। लेकिन मुझे बात समझ में आ गई।
गिदरीस किरीस

0

मैं ओपी की हताशा को पूरी तरह से समझता हूं। मैं निम्नलिखित का उपयोग करता हूं:

उपयोगिता समारोह :

/**
 * 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;
}

उपयोग :

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

ध्यान दें कि यह फ़ंक्शन अभी तक नेस्टेड नियंत्रणों के लिए पूरा नहीं करता है।


0

इस रत्न को देखें । अब तक का सबसे सुरुचिपूर्ण समाधान मैंने देखा है।

पूर्ण कोड

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

राय:

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

एपीआई

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

मैंने प्रस्तुत उत्तरों में कुछ बदलावों के साथ एक संस्करण बनाया है, जो उन लोगों के लिए हैं जो कोणीय के संस्करण 8 से पुराने संस्करणों का उपयोग कर रहे हैं, मैं इसे उन लोगों के साथ साझा करना चाहूंगा जो उपयोगी हैं।

उपयोगिता समारोह:

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;
}

उपयोग:

onSubmit() {
 if (this.isValidForm()) {
   // ... TODO: logic if form is valid
 }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.