반응 양식-필드를 터치 한 것으로 표시


84

모든 양식의 필드를 터치 한 것으로 표시하는 방법을 찾는 데 문제가 있습니다. 주요 문제는 필드를 만지지 않고 양식을 제출하려고하면 유효성 검사 오류가 표시되지 않는다는 것입니다. 컨트롤러에 해당 코드에 대한 자리 표시자가 있습니다.
내 생각은 간단합니다.

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

답변:


145

다음 함수는 양식 그룹의 컨트롤을 반복하여 부드럽게 터치합니다. 컨트롤 필드가 객체이기 때문에 코드는 양식 그룹의 컨트롤 필드에서 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);
      }
    });
  }

18
이 슬프게 Internet Explorer에서하지 작업 :( 단순히 변경 않습니다 (<any>Object).values(formGroup.controls)Object.keys(formGroup.controls).map(x => formGroup.controls[x])(에서 stackoverflow.com/questions/42830257/... )
moi_meme

1
이것은 FormGroup과 FormControl을 사용하는 데 큰 도움이되었고 사용자에게 필수 필드를 건드리지 않았 음을 보여주는 방법을 궁금해했습니다. 감사합니다.
NAMS 2017

@NAMS 문제 없어! 도움이 돼 기쁩니다 :]
masterwok

4
+1 재귀 부분에서 한 가지 사소한 문제입니다. controls함수의 시작 부분에서 이미 반복하고 있으므로 대신 다음과 같아야합니다.if (control.controls) { markFormGroupTouched(control); }
zurfyx

3
touched입력이 한 번 흐려졌다는 의미입니다. 오류를 표시하려면 updateValueAndValidity()컨트롤 도 호출해야했습니다 .
adamdport

103

에서 각도 8/9 당신은 간단하게 사용할 수 있습니다

this.form.markAllAsTouched();

컨트롤 및 하위 컨트롤을 터치로 표시합니다.

AbstractControl 문서


2
이것은 Angular 8을 사용하는 사람들에게 허용되는 대답이어야합니다.
Jacob Roberts

1
이것은 더 간단하고 깨끗한 솔루션입니다.
HDJEMAI

1
이것은 각도 8 이상에 권장되는 솔루션입니다.
Duc Nguyen

1
이것이 일부 컨트롤에서 작동하지 않는 것 같으면 해당 FormGroup에 없을 수 있습니다.
Noumenon

11

@masterwork의 답변에 관해서. 이 솔루션을 시도했지만이 줄에서 FormGroup 대신 FormControl 인수를 전달하기 때문에 함수가 FormGroup 내부에서 재귀 적으로 파기하려고 할 때 오류가 발생했습니다.

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


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

1
이것은 당신이 경우 작동하지 않는 formGroup다른 포함 formGroup
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

나는이 문제가 있었지만 내가 발견 한 Angular 튜토리얼이 아니었지만 "올바른"방법을 찾았다.

HTML의 form태그 #myVariable='ngForm'에서 반응 양식 예제에서 사용하는 것 외에도 템플릿 기반 양식 예제에서 사용 하는 것과 동일한 템플릿 참조 변수 ( 'hashtag'변수)를 추가합니다.

<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가 해당 요소를 구동하는 Directive 대신 HtmlElement로 설정되므로 작동하지 않습니다.

그것이 알고 myFormGroup반응 양식 자습서 당 구성 요소의 타이프 코드에서 볼 수 있지만, myForm당신은 같은 메소드 호출을 통해 그것을 통과하지 않는 한, 아니다 submit(myForm)submit(myForm: NgForm): void {...}. (알림 NgForm은 타이프 스크립트의 제목 대문자이지만 HTML의 경우 카멜 케이스입니다.)


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

그냥 시도했지만 어떻게 든 자식 양식 요소를 건드리지 않습니다. 모든 자식 요소를 수동으로 표시하는 루프를 작성해야했습니다. 왜 markAsTouched()자식 요소를 건드리지 않았 는지 단서 가 있습니까?
Giedrius Kiršys 2014

어떤 각도 버전을 사용하고 있습니까?
Vlado Tesanovic 2016

각 버전은 2.1.0이다
Giedrius Kiršys

1
markAsTouched()자식 요소를 표시하지 않는 이유를 찾은 것 같습니다 -github.com/angular/angular/issues/11774 . TL; DR : 버그가 아닙니다.
Giedrius Kiršys 2011

1
네, 이제 기억납니다. 양식이 유효하지 않은 경우 제출 버튼을 비활성화 할 수 있습니다. <button [disable] = "! this.form"> 제출 </ button>
Vlado Tesanovic 2016

1

같은 문제가 발생했지만이를 처리하는 코드로 구성 요소를 "오염"시키고 싶지 않습니다. 특히 나는 이것을 여러 형태로 필요로하고 여러 번 코드를 반복하고 싶지 않기 때문에.

따라서 나는 (지금까지 게시 된 답변을 사용하여) 지시문을 만들었습니다. 지시문은 NgForm의 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();
        }
      });
    }
  }

이 솔루션은 FormGroup과 FormArray 모두에서 작동합니다.

여기에서 놀 수 있습니다 : angular-mark-as-touched


@VladimirPrudnikov 문제는 함수에 대한 재귀 호출을 할 때 일반적으로 더 많은 오버 헤드가 관련된다는 것입니다. 따라서 CPU는 호출 스택을 처리하는 데 더 많은 시간을 소비합니다. 루프를 사용할 때 CPU는 알고리즘 자체를 수행하는 데 대부분의 시간을 소비합니다. 재귀의 장점은 일반적으로 코드가 더 읽기 쉽다는 것입니다. 따라서 성능이 문제가 아니라면 재귀를 고수 할 수 있다고 말하고 싶습니다.
아서 실바

"조기 최적화는 모든 악의 근원입니다."
Dem Pilafian 19

@DemPilafian 견적에 동의합니다. 그러나 여기에는 적용되지 않습니다. 누군가이 스레드에 접근하면 최적화 된 솔루션을 무료로 얻을 수 있기 때문입니다 (시간을 들이지 않아도 됨). 그리고, BTW, 내 경우에는 정말 한 이유는) = 그것을 최적화
아서 실바

1

@masterwork에 따라

각도 버전 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>


이것은 새 유효성 검사 규칙을 추가 할 때 시간이 지남에 따라 무거워 질 수있는 것처럼 보입니다. 그러나 나는 요점을 얻었다.
Giedrius Kiršys

0

나는 OP의 좌절감을 완전히 이해합니다. 다음을 사용합니다.

유틸리티 기능 :

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

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

앵귤러 버전 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
 }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.