如何避免在角度应用程序中重复表单字段和表单的验证?

问题描述 投票:1回答:1

我是棱角分明的新手。

我正在构建一个具有联系和订阅形式的应用程序,这在网站中很常见。这两种形式都有名字,姓氏和电子邮件字段。我正在为这两种形式复制html标记和验证。

所以我决定将公共字段提取到一个单独的组件,以避免重复,但我很难将值从子组件传递给父组件,并保持提交按钮禁用,以及子组件中的无效输入。

我目前的代码如下。

personalinfo.component.html

<div [formGroup]="personalInfoForm" novalidate>
  <div class="firstName">
    <mat-form-field>
      <input matInput placeholder="First Name" 
             formControlName="firstName">

      <mat-error [hidden]="personalInfoForm.hasError('required', 'firstName')">
        Please provide first name
      </mat-error>

    </mat-form-field>
  </div>

  <div class="lastName">
    <mat-form-field>
      <input matInput placeholder="Last Name" formControlName="lastName">
      <mat-error [hidden]="personalInfoForm.hasError('required', 'lastName')">
        Please provide last name
      </mat-error>
    </mat-form-field>
  </div>

  <div class="email">
    <mat-form-field>
      <input matInput placeholder="Email" formControlName="email">

      <mat-error *ngIf="personalInfoForm.hasError('required', 'email')">
        Please provide an email
      </mat-error>
      <mat-error *ngIf="personalInfoForm.hasError('pattern', 'email') && 
                       !personalInfoForm.hasError('required', 'email')">
        Invalid email
      </mat-error>
    </mat-form-field>
  </div>
</div>

personalinfo.component.ts

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

@Component({
  selector: 'app-personalinfo',
  templateUrl: './personalinfo.component.html',
  styleUrls: ['./personalinfo.component.scss']
})
export class PersonalinfoComponent implements OnInit {

  readonly EMAIL_REGEX = "[a-zA-Z0-9.-_]{1,}@[a-zA-Z0-9.-_]{1,}.[a-zA-Z]{2,}";


  personalInfoForm: FormGroup;

  constructor(private fb: FormBuilder) {

    this.createPersonalInfoForm();
   }


   createPersonalInfoForm() {

    this.personalInfoForm = this.fb.group({
      firstName: ['', Validators.required],
      lastName: ['', Validators.required],
      email: ['', [Validators.required, Validators.pattern(this.EMAIL_REGEX)]],

    });
  }
  ngOnInit() {
  }

}

contact.component.html

<div class="form-contact" flexLayout="row" fxLayoutAlign="center">


  <form [formGroup]="contactForm" 
        novalidate 
        (ngSubmit)="sendMessage(contactForm.value, contactForm.invalid)">


    <app-personalinfo></app-personalinfo> 



    <div class="subject">
      <mat-form-field>
        <input matInput
               placeholder="Subject"
               formControlName="subject" >
        <mat-error   *ngIf="contactForm.hasError('required', 'subject')">
          Please provide a subject
        </mat-error>
        <mat-error   *ngIf ="contactForm.hasError('minlength', 'subject') &&
                            !contactForm.hasError('required', 'subject')">
          Minimum 5 characters
        </mat-error>

      </mat-form-field>
    </div>

    <div class="message">
      <mat-form-field>
        <textarea matInput 
                  placeholder="Message" 
                  matTextareaAutosize 
                  matAutosizeMinRows="5" 
                  matAutosizeMaxRows="10"
                  formControlName="message">
        </textarea>
        <mat-error  *ngIf ="contactForm.hasError('required', 'message')">
          Please provide a message
        </mat-error>
        <mat-error   *ngIf ="contactForm.hasError('minlength', 'message') &&
                            !contactForm.hasError('required', 'message')">
          Minimum 10 characters
        </mat-error>
      </mat-form-field>
    </div>

    <button mat-button 
            color="primary" 
            type="submit" 
            [disabled]="contactForm.invalid">
            Submit
    </button>

  </form>

</div>

contact.component.ts

import { Component, OnInit } from '@angular/core';
import { FormControl, Validators, FormBuilder, FormGroup } from '@angular/forms';
// interface with fields (firstName, lastName and etc)
import { User } from '../user';

@Component({
  selector: 'app-contact',
  templateUrl: './contact.component.html',
  styleUrls: ['./contact.component.scss']
})
export class ContactComponent implements OnInit {


  contactForm: FormGroup;

  constructor(private fb: FormBuilder) {

    this.createContactForm();
   }


   createContactForm() {
    this.contactForm = this.fb.group({
      subject: ['', [Validators.required, Validators.minLength(5)]],
      message: ['', [Validators.required, Validators.minLength(10)]]
    });
  }


  ngOnInit() {
  }

  sendMessage(user, isInvalid: boolean)
  {
      if(!isInvalid){

        console.log(user);

         console.log(user.firstName);
         console.log(user.lastName);
         console.log(user.email);
         console.log(user.subject)
         console.log(user.message);

      }
  }

}

我发现了几个问题,但事情并不清楚。任何帮助将受到高度赞赏。

angular
1个回答
1
投票

只需为共享表单创建可重用的组件即可。

共享form.component.ts

在你的shared-form.component中,你定义了你的共享FormControls

  • 名字
  • 电子邮件

接下来你需要掌握你的组件中的FormGroupDirective。使用此指令,您可以在Submit范围之外调用Reset[formGroup]

通过此设置,您可以使用SharedForm进行远程控制! :)

你的shared-form.component.ts看起来像这样:

import { Component, ViewChild, Optional } from '@angular/core';
import { FormGroup, FormBuilder, FormGroupDirective, Validators, NgForm } from '@angular/forms';

@Component({
  selector: 'app-shared-form',
  templateUrl: './shared-form.component.html',
  styles: []
})
export class SharedFormComponent {

  @ViewChild(FormGroupDirective) fgd: FormGroupDirective;

  public form: FormGroup;

  get emailCtrl() {
    return this.form.get('email');
  }

  constructor(private fb: FormBuilder,
              @Optional() fgdParent: FormGroupDirective) {
    this.createForm();
    if (fgdParent) {
      fgdParent.ngSubmit.subscribe(() => this.fgd.onSubmit(null));
    }
  }

  createForm() {
    this.form = this.fb.group({
      firstName: ['', Validators.required],
      lastName: ['', Validators.required],
      email: ['', [Validators.required, Validators.email]],
    });
  }
}

shared-form.component.html

<div [formGroup]="form" novalidate>
  <div class="firstName">
    <mat-form-field>
      <input matInput placeholder="First Name"
             formControlName="firstName">
      <mat-error>
        Please provide first name
      </mat-error>
    </mat-form-field>
  </div>

  <div class="lastName">
    <mat-form-field>
      <input matInput placeholder="Last Name" formControlName="lastName">
      <mat-error [hidden]="form.hasError('required', 'lastName')">
        Please provide last name
      </mat-error>
    </mat-form-field>
  </div>

  <div class="email">
    <mat-form-field>
      <input matInput placeholder="Email" formControlName="email">
      <mat-error *ngIf="emailCtrl.hasError('required')">
        Please provide an email
      </mat-error>
      <mat-error *ngIf="emailCtrl.hasError('email') && !emailCtrl.hasError('required')">
        Invalid email
      </mat-error>
    </mat-form-field>
  </div>
</div>

personalinfo.component.ts

你的PersonalinfoComponent只不过是你的SharedForm的包装

personalinfo.component.html

<mat-card>
  <h3>Subscribe</h3>

  <mat-card-content>
    <app-shared-form #form></app-shared-form>
  </mat-card-content>

  <mat-card-actions>
    <button mat-raised-button color="primary" type="button" (click)="onSubmit()">Submit</button>
    <button mat-raised-button type="button" (click)="form.fgd.resetForm()">Reset</button>
  </mat-card-actions>
</mat-card>

personalinfo.component.ts

import { Component, ViewChild } from '@angular/core';
import { SharedFormComponent } from '../shared/shared-form/shared-form.component';

@Component({
  selector: 'app-personalinfo',
  templateUrl: './personalinfo.component.html',
  styleUrls: ['./personalinfo.component.css']
})
export class PersonalinfoComponent {

  @ViewChild(SharedFormComponent) formComp: SharedFormComponent;

  onSubmit() {
    this.formComp.fgd.onSubmit(null);
    if (!this.formComp.form.valid) { return; }
    console.log('Ready to make an ajax call: ', this.formComp.form.getRawValue());
  }
}

如果表格无效,我不是禁用按钮的忠实粉丝。因为用户在尝试点击“提交”按钮时看不到任何验证错误。更好的用户体验是,如果用户点击提交按钮,表单将触发验证,用户可以看到表单有什么问题。这是在Angular Material FormField中完全实现的。

有几个例子说明如何触发从ParentComponentChildComponent的提交事件。当我们通过ChildComponent-Decorator获取ViewChild时,我们可以在onSubmit()上调用FormGroupDirective并且验证被触发。要重置表单,只需调用resetForm()


contact.component.ts

ContactComponent的用例与PersonalinfoComponent非常相似。

就视图呈现而言,我们可以将ChildForm(SharedFormComponent)注入我们的ParentForm(ContactForm)

ngAfterViewInit() {
  this.contactForm.addControl('personalInfo', this.childForm.form);
}

目前,当且仅当子表格有效时,联系表格才有效。

但这里非常酷的东西又是Angular强大的DI。

如果我们在Application中大量使用SharedForm,我们可以在SharedFormComponent中注入父FormGroupDirective作为可选依赖项。因此,每当SharedFormComponent被包装在父FormGroup中时,它就会自动获取父FormGroupDirective。现在我们可以将父事件的提交事件委托给孩子。

constructor(private fb: FormBuilder,
            @Optional() fgdParent: FormGroupDirective) {
  this.createForm();
  if (fgdParent) {
    fgdParent.ngSubmit.subscribe(() => this.fgd.onSubmit(null));
  }
}

contact.component.ts

import { SharedFormComponent } from './../shared/shared-form/shared-form.component';
import { FormGroup, FormBuilder, Validators, NgForm } from '@angular/forms';
import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-contact',
  templateUrl: './contact.component.html',
  styleUrls: ['./contact.component.css']
})
export class ContactComponent implements AfterViewInit {

 @ViewChild(SharedFormComponent) childForm: SharedFormComponent;

  contactForm: FormGroup;

  constructor(private fb: FormBuilder) {
    this.createContactForm();
  }

  createContactForm() {
    this.contactForm = this.fb.group({
      subject: ['', [Validators.required, Validators.minLength(5)]],
      message: ['', [Validators.required, Validators.minLength(10)]]
    });
  }

  ngAfterViewInit() {
    this.contactForm.addControl('personalInfo', this.childForm.form);
  }

  onSubmit() {
    if (!this.contactForm.valid) {
      return;
    }
    console.log('Contact Form is Valid!', this.contactForm.getRawValue());
  }

  onReset(fgd: NgForm) {
    fgd.onReset();
    this.childForm.fgd.onReset();
  }
}

contact.component.html

<mat-card>
  <mat-card-title>
    <h3>Contact</h3>
  </mat-card-title>

  <mat-card-content>
    <form [formGroup]="contactForm" #fgd="ngForm" novalidate (ngSubmit)="onSubmit()">

      <app-shared-form #childForm></app-shared-form>

      <div class="subject">
        <mat-form-field>
          <input matInput placeholder="Subject" formControlName="subject">
          <mat-error *ngIf="contactForm.hasError('required', 'subject')">
            Please provide a subject
          </mat-error>
          <mat-error *ngIf="contactForm.hasError('minlength', 'subject') &&
                    !contactForm.hasError('required', 'subject')">
            Minimum 5 characters
          </mat-error>

        </mat-form-field>
      </div>

      <div class="message">
        <mat-form-field>
          <textarea matInput placeholder="Message" matTextareaAutosize matAutosizeMinRows="5" matAutosizeMaxRows="10" formControlName="message">
      </textarea>
          <mat-error *ngIf="contactForm.hasError('required', 'message')">
            Please provide a message
          </mat-error>
          <mat-error *ngIf="contactForm.hasError('minlength', 'message') &&
                    !contactForm.hasError('required', 'message')">
            Minimum 10 characters
          </mat-error>
        </mat-form-field>
      </div>

    </form>
  </mat-card-content>

  <mat-card-actions>
    <button mat-raised-button (click)="fgd.onSubmit(null)" color="primary" type="button">
      Submit
    </button>
    <button mat-raised-button type="reset" (click)="onReset(fgd)">Reset</button>
  </mat-card-actions>
</mat-card>

我创建了一个小的工作示例,使更多的东西更容易理解:https://stackblitz.com/github/SplitterAlex/stackoverflow-48931808

只需按照此处的源代码:https://github.com/SplitterAlex/stackoverflow-48931808

© www.soinside.com 2019 - 2024. All rights reserved.