bestsource

반응형 양식 - 필드를 터치됨으로 표시

bestsource 2023. 4. 24. 23:42
반응형

반응형 양식 - 필드를 터치됨으로 표시

모든 폼의 필드를 터치된 것으로 표시하는 방법을 찾는 데 어려움을 겪고 있습니다.가장 큰 문제는 필드를 터치하지 않고 폼을 제출하려고 하면 인증 오류가 나타나지 않는다는 것입니다.내 컨트롤러에 그 코드 조각의 자리 표시자가 있어
제 생각은 간단합니다.

  1. 사용자 클릭 전송 버튼
  2. 모든 필드가 터치된 것으로 표시됨
  3. error formatter가 다시 실행되어 검증 오류를 표시합니다.

새로운 방법을 도입하지 않고 제출 시 오류를 표시하는 방법을 알고 계신 분은 공유해 주십시오.감사합니다!


단순화된 폼:

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

Angular 8부터는 간단하게 사용할 수 있습니다.

this.form.markAllAsTouched();

컨트롤과 그 하위 컨트롤에 터치 표시를 합니다.

Abstract Control 문서

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

@masterwork의 답변에 대해서.이 솔루션을 시도했지만 함수가 FormGroup 내부를 재귀적으로 파려고 했을 때 오류가 발생했습니다.이것은 다음 행에 FormGroup이 아닌 FormControl 인수가 전달되어 있기 때문입니다.

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

Angular v8에서는 이 기능을 내장하고 있습니다.markAllAsTouched방법.

예를 들어 다음과 같이 사용할 수 있습니다.

form.markAllAsTouched();

다음 공식 문서를 참조하십시오.https://angular.io/api/forms/AbstractControl#markallastouched

폼 컨트롤을 루프하여 터치 마크를 붙이는 것도 가능합니다.

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

이것이 나의 해결책이다.

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

이 문제가 있었지만, 지금까지 Angular 튜토리얼에는 없는 "올바른" 방법을 찾았습니다.

HTML에서form태그, 동일한 템플릿 참조 변수 추가#myVariable='ngForm'Reactive Forms 예제에서 사용하는 것 외에 템플릿 기반 양식 예제에서 사용하는 ('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"part. 를 사용하는 경우#myFormvar가 HtmlElement로 설정되기 때문에 HtmlElement를 구동하는 Directive가 아닌 HtmlElement로 설정되기 때문에 기능하지 않습니다.

알아두시오myFormGroup는 Reactive Forms 튜토리얼에 따라 컴포넌트의 타이프스크립트 코드에 표시됩니다만,myForm 않아요, 한. 예를 들어 메서드 호출을 통해 전달하지 않는 한submit(myForm)로로 합니다.submit(myForm: NgForm): void {...} ((림)))NgForm입니다.)

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

같은 문제가 발생했지만 이 문제를 처리하는 코드로 컴포넌트를 오염시키고 싶지 않습니다.특히 저는 여러 형태로 이것이 필요하고 여러 번 반복하고 싶지 않기 때문입니다.

그래서 지시문을 작성했습니다(지금까지 게재된 답변으로).의 'NgForm'을합니다.onSubmit- : - :하지 않은 경우 제출을합니다.양식이 비활성화되면 모든 필드가 터치된 것으로 표시되며 제출이 중단됩니다.submit-Method on-Submit-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>

이것이 제가 실제로 사용하고 있는 코드입니다.

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

이 코드는 유효합니다.

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

재귀 없는 솔루션

성능에 대해 우려하시는 분들을 위해 재귀를 사용하지 않는 솔루션을 생각해 냈습니다. 그러나 재귀는 여전히 모든 수준의 모든 제어에 걸쳐 반복됩니다.

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

이 솔루션은 Form Group과 Form Array 모두에서 작동합니다.

여기서 놀 수 있다: 만지면 각진 마크

@masterwork에 따라

각도 버전 8의 typscript 코드

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

내가 하는 방법은 이렇다.제출 버튼을 누르거나 양식을 터치할 때까지 오류 필드를 표시하지 않도록 합니다.

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>

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

이 기능은 아직 중첩된 컨트롤을 지원하지 않습니다.

보석을 보세요.지금까지 본 것 중 가장 우아한 해결책입니다.

풀코드

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

표시:

<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 

제시된 답변에 변화를 준 버전을 만들었는데, angular 버전 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
 }
}

각도 13:

this.form.markAsDirty();
this.form.markAllAsTouched();
@Component()
export class AppComponent {
  public loginForm: FormGroup = new FormGroup({
    email: new FormControl('', Validators.required),
    password: new FormControl('', Validators.required)
  });

  public onSubmit(): void {
    this.loginForm.markAllAsTouched();  // calling mark as touch every time.
    if(this.loginForm.valid) { ... }
  }
}

언급URL : https://stackoverflow.com/questions/40529817/reactive-forms-mark-fields-as-touched

반응형