Angular 4中的表单简介:反应形式

最终产品图片
您将要创造的

这是“ Angular 4中的表单简介”系列的第二部分。在第一部分中,我们使用模板驱动的方法创建了一个表单。 我们使用了ngModelngModelGroupngForm等指令来ngModel表单元素。 在本教程中,我们将采用另一种方法来构建表单-反应性方法。

反应形式

与模板驱动的表单相比,反应性表单采用不同的方法。 在这里,我们在组件类中创建并初始化表单控件对象 。 它们是保存表单状态的中间对象。 然后,我们将它们绑定到模板中的表单控件元素

表单控件对象侦听输入控制值的任何更改,并且它们会立即反映在对象的状态中。 由于组件可以直接访问数据模型结构,因此所有更改都可以在数据模型,表单控制对象和输入控制值之间同步。

使用模型驱动的方法对响应式表单进行高级概述

实际上,如果我们正在构建用于更新用户配置文件的表单,则数据模型是从服务器检索的用户对象。 按照约定,这通常存储在组件的用户属性( this.user )中。 表单控件对象或表单模型将绑定到模板的实际表单控件元素。

这两个模型即使不相同,也应具有相似的结构。 但是,输入值不应直接流入数据模型。 该图像描述了用户从模板输入的信息如何进入表单模型。

让我们开始吧。

先决条件

您无需遵循本系列的第一部分,而使第二部分有意义。 但是,如果您不熟悉Angular中的表单,我强烈建议您采用模板驱动策略。 我的GitHub存储库中提供了该项目的代码。 确保您位于正确的分支上,然后下载zip或克隆存储库以查看正在使用的表单。

如果您想从头开始,请确保已安装Angular CLI。 使用ng命令生成一个新项目。

$ ng new SignupFormProject

接下来,为SignupForm生成一个新组件或手动创建一个组件。

ng generate component SignupForm

用以下内容替换app.component.html的内容:

<app-signup-form> </app-signup-form>

这是src /目录的目录结构。 我删除了一些不必要的文件,以使事情变得简单。

.
├── app
│   ├── app.component.css
│   ├── app.component.html
│   ├── app.component.ts
│   ├── app.module.ts
│   ├── signup-form
│   │   ├── signup-form.component.css
│   │   ├── signup-form.component.html
│   │   └── signup-form.component.ts
│   └── User.ts
├── index.html
├── main.ts
├── polyfills.ts
├── styles.css
├── tsconfig.app.json
└── typings.d.ts

如您所见, SignupForm组件的目录已自动创建。 那就是我们大多数代码的去向。 我还创建了一个新的User.ts来存储我们的User模型。

HTML模板

在深入研究实际的组件模板之前,我们需要对要构建的内容有一个抽象的想法。 这就是我脑海中的表单结构。 注册表单将具有几个输入字段,一个select元素和一个checkbox元素。

HTML模板

这是我们将用于注册页面HTML模板。

HTML模板
<div class="row custom-row">
  <div class= "col-sm-5 custom-container jumbotron">
      
    <form class="form-horizontal">
        <fieldset>
    	  <legend>SignUp</legend>
        
            <!--- Email Block --->
            <div class="form-group">
    	      <label for="inputEmail">Email</label>
    		  <input type="text"
                id="inputEmail"
    	        placeholder="Email">
    	   	</div>
            <!--- Password Block --->
    	   	<div class="form-group">
    	      <label for="inputPassword">Password</label>
    	      <input type="password" 
                id="inputPassword"
                placeholder="Password">
    	    </div>
    
    	    <div class="form-group">
    	      <label for="confirmPassword" >Confirm Password</label>
    	      <input type="password" 
                id="confirmPassword"
                placeholder="Password">
    	    </div>
            
            <!--- Select gender Block --->
    	    <div class="form-group">
    	      <label for="select">Gender</label>
    	        <select id="select">
    	          <option>Male</option>
    	          <option>Female</option>
    	          <option>Other</option>
    	        </select>
    	    </div>
            
            <!--- Terms and conditions Block --->
             <div class="form-group checkbox">
              <label>
                <input type="checkbox"> Confirm that you've read the Terms and 
                Conditions
              </label>
            </div>
    	   
           <!--- Buttons Block --->
    	    <div class="form-group">
    	        <button type="reset" class="btn btn-default">Cancel</button>
    	        <button type="submit" class="btn btn-primary">Submit</button>
    	    </div>
    	</fieldset>
    </form>
  </div>
</div>

HTML模板中使用CSS类是用于使外观漂亮的Bootstrap库的一部分。 由于这不是设计教程,因此除非有必要,否则我不会谈论表单CSS方面。

基本表格设置

要创建Reactive表单,您需要从@angular/forms导入ReactiveFormsModule并将其添加到app.module.ts中的imports数组中。

app / app.module.ts
// Import ReactiveFormsModule
import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
  .
  .
  //Add the module to the imports Array
  imports: [
    BrowserModule,
    ReactiveFormsModule
 .
 .
})
export class AppModule { }

接下来,为注册表单创建一个用户模型。 我们可以使用类或接口来创建模型。 对于本教程,我将导出具有以下属性的类。

app / User.ts
export class User {

    id: number;
    email: string;
    //Both the passwords are in a single object
    password: { 
	  pwd: string;
	  confirmPwd: string;
	};
    
	gender: string;
    terms: boolean;

	constructor(values: Object = {}) {
	  //Constructor initialization
      Object.assign(this, values);
  }

}

现在,在SignupForm组件中创建User模型的实例。

app / signup-form / signup-form.component.ts
import { Component, OnInit } from '@angular/core';
// Import the User model
import { User } from './../User';

@Component({
  selector: 'app-signup-form',
  templateUrl: './signup-form.component.html',
  styleUrls: ['./signup-form.component.css']
})
export class SignupFormComponent implements OnInit {

  //Gender list for the select control element
  private genderList: string[];
  //Property for the user
  private user:User;

  ngOnInit() {

    this.genderList =  ['Male', 'Female', 'Others'];
  
   
}

对于signup-form.component.html文件,我将使用上面讨论的相同HTML模板,但会有一些细微变化。 注册表单具有一个带有选项列表的选择字段。 尽管这ngFor ,但我们将通过使用ngFor指令遍历列表来以Angular方式进行ngFor

app / signup-form / signup-form.component.html
<div class="row custom-row">
  <div class= "col-sm-5 custom-container jumbotron">
      
    <form class="form-horizontal">
        <fieldset>
          <legend>SignUp</legend>
.
.
            <!--- Gender Block -->
            <div class="form-group">
              <label for="select">Gender</label>
                   <select id="select">
        	         
        	         <option *ngFor = "let g of genderList" 
        	           [value] = "g"> {{g}} 
        	         </option>
        	       </select>
        	   </div>
.
.
    </fieldset>
    </form>
  </div>
</div>

注意:您可能会收到一条错误消息,指出 没有为ControlContainer提供程序 当组件具有不带formGroup指令的<form>标记时,将出现错误。 一旦在本教程的后面添加了FormGroup指令,该错误将消失。

我们手边有一个组件,一个模型和一个表单模板。 现在怎么办? 现在是时候让我们变脏了,熟悉创建反应式表单所需的API。 这包括FormControlFormGroup

使用FormControl跟踪状态

使用反应式表单策略构建表单时,您不会遇到ngModel和ngForm指令。 相反,我们使用基础的FormControl和FormGroup API。

FormControl是用于创建FormControl实例的指令,可用于跟踪特定表单元素的状态及其验证状态。 这是FormControl的工作方式:

/* Import FormControl first */
import { FormControl } from '@angular/forms';

/* Example of creating a new FormControl instance */
export class SignupFormComponent {
  email = new FormControl();
}

email现在是一个FormControl实例,您可以将其绑定到模板中的输入控件元素,如下所示:

<h2>Signup</h2>

<label class="control-label">Email:
  <input class="form-control" [formControl]="email">
</label>

现在,模板表单元素已绑定到组件中的FormControl实例。 这意味着对输入控制值的任何更改都会反映在另一端。

FormControl构造函数接受三个参数-初始值,同步验证器数组和异步验证器数组-正如您可能已经猜到的,它们都是可选的。 我们将在这里讨论前两个论点。

import { Validators } from '@angular/forms';
.
.
.
/* FormControl with initial value and a validator */

  email = new FormControl('bob@example.com', Validators.required);

Angular具有一组有限的内置验证器。 流行的验证器方法包括Validators.requiredValidators.minLengthValidators.maxlengthValidators.pattern 。 但是,要使用它们,必须首先导入Validator API。

对于我们的注册表单,我们有多个输入控制字段(用于电子邮件和密码),一个选择器字段和一个复选框字段。 而不是创建单个FormControl对象,将所有这些FormControl归为一个实体更有意义吗? 这是有益的,因为我们现在可以在一处跟踪所有子FormControl对象的值和有效性。 那就是FormGroup目的。 因此,我们将使用多个子FormControl注册一个父FormGroup。

使用FormGroup将多个FormControl分组

要添加FormGroup,请首先将其导入。 接下来,将signupForm声明为类属性,并按如下所示对其进行初始化:

app / signup-form / signup-form.component.ts
//Import the API for building a form
import { FormControl, FormGroup, Validators } from '@angular/forms';


export class SignupFormComponent implements OnInit {
    
    genderList: String[];
    signupForm: FormGroup;
    .
    .

   ngOnInit() {

    this.genderList =  ['Male', 'Female', 'Others'];

    this.signupForm = new FormGroup ({
    	email: new FormControl('',Validators.required),
		pwd: new FormControl(),
		confirmPwd: new FormControl(),
		gender: new FormControl(),
		terms: new FormControl()
	})
  
   }
}

如下所示将FormGroup模型绑定到DOM:

app / signup-form / signup-form.component.html
<form class="form-horizontal"  [formGroup]="signupForm" >
        <fieldset>
    	  <legend>SignUp</legend>
        
            <!--- Email Block -->
            <div class="form-group">
    	      <label for="inputEmail">Email</label>
    		  <input type="text" formControlName = "email"
                id="inputEmail"
    	        placeholder="Email">
            
            .
            .
        
        </fieldset>
    </form>

[formGroup] = "signupForm"告诉Angular您想将此表单与组件类中声明的FormGroup关联。 当Angular看到formControlName="email" ,它将检查父FormGroup中具有键值email的FormControl实例。

同样,通过添加formControlName="value"属性来更新其他表单元素,就像我们在这里所做的那样。

若要查看是否一切正常,请在form标记后添加以下内容:

app / signup-form / signup-form.component.html
<!--- Log the FormGroup values to see if the binding is working -->
    <p>Form value {{ signupForm.value | json }} </p>
     <p> Form status {{ signupForm.status | json}} </p>

通过JsonPipe传递SignupForm属性,以在浏览器中将模型呈现为JSON。 这对于调试和记录很有帮助。 您应该看到这样的JSON输出。

模型驱动表单中的表单状态和有效性

这里有两件事要注意:

  1. JSON与我们之前创建的用户模型的结构不完全匹配。
  2. signupForm.status显示表单的状态为INVALID。 这清楚地表明,电子邮件控制字段上的Validators.required可以正常工作。

表单模型和数据模型的结构应匹配。

// Form model
 { 
    "email": "", 
    "pwd": "", 
    "confirmPwd": "", 
    "gender": "", 
    "terms": false 
}

//User model
{
    "email": "",
    "password": { 
	  "pwd": "",
	  "confirmPwd": "",
	},
	"gender": "",
    "terms": false
}

为了获得数据模型的层次结构,我们应该使用嵌套的FormGroup。 此外,在单个FormGroup下具有相关的表单元素总是一个好主意。

嵌套表格组

为密码创建一个新的FormGroup。

app / signup-form / signup-form.component.ts
this.signupForm = new FormGroup ({
    	email: new FormControl('',Validators.required),
		password: new FormGroup({
			pwd: new FormControl(),
			confirmPwd: new FormControl()
		}),
		gender: new FormControl(),
		terms: new FormControl()
	})

现在,要将新表单模型与DOM绑定,请进行以下更改:

app / signup-form / signup-form.component.html
<!--- Password Block -->
    <div formGroupName = "password">
	   	<div class="form-group">
	      <label for="inputPassword">Password</label>
	      <input type="password" formControlName = "pwd"
            id="inputPassword"
            placeholder="Password">
	    </div>

	    <div class="form-group">
	      <label for="confirmPassword" >Confirm Password</label>
	      <input type="password" formControlName = "confirmPwd"
            id="confirmPassword"
            placeholder="Password">
	    </div>
    </div>

formGroupName = "password"对嵌套的FormGroup执行绑定。 现在,表单模型的结构符合我们的要求。

Form value: { 
    "email": "", "
    password": { "pwd": null, "confirmPwd": null }, 
    "gender": null, 
    "terms": null 
    }

Form status "INVALID"

接下来,我们需要验证表单控件。

验证表格

我们已经对电子邮件输入控件进行了简单的验证。 但是,这还不够。 这是我们验证要求的完整列表。

  • 所有表单控制元素都是必需的
  • 禁用提交按钮,直到表单状态为VALID。
  • 电子邮件字段应严格包含电子邮件ID。
  • 密码字段的最小长度为8。
验证表格

第一个很简单。 将Validator.required添加到表单模型中的所有FormControls中。

app / signup-form / signup-form.component.ts
this.signupForm = new FormGroup ({
		email: new FormControl('',Validators.required),
		password: new FormGroup({
			pwd: new FormControl('', Validators.required),
			confirmPwd: new FormControl('', Validators.required)
		}),
		gender: new FormControl('', Validators.required),
        //requiredTrue so that the terms field isvalid only if checked
		terms: new FormControl('', Validators.requiredTrue)
	})

接下来,在表单无效时禁用按钮。

app / signup-form / signup-form.component.html
<!--- Buttons Block -->
    <div class="form-group">
        <button type="reset" class="btn btn-default">Cancel</button>
        <button type="submit" [disabled] = "!signupForm.valid" class="btn btn-primary">Submit</button>
    </div>

要对电子邮件添加约束,您可以使用默认的Validators.email或创建一个自定义的Validators.pattern()来指定正则表达式,例如以下表达式:

email: new FormControl('',
    [Validators.required, 
    Validators.pattern('[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$')])

minLength验证器用于密码字段。

password: new FormGroup({
    		pwd: new FormControl('', [Validators.required, Validators.minLength(8)]),
			confirmPwd: new FormControl('', [Validators.required, Validators.minLength(8)])
		}),

验证就可以了。 但是,表单模型逻辑显得混乱且重复。 让我们先清理一下。

使用FormBuilder重构代码

Angular为您提供了一个语法糖,用于创建称为FormBuilder的FormGroup和FormControl的新实例。 除了我们在这里介绍的内容外,FormBuilder API并没有做任何特别的事情。

它简化了我们的代码,并使构建表单的过程更加轻松。 要创建FormBuilder,必须将其导入signup-form.component.ts并将FormBuilder注入构造函数。

app / signup-form / signup-form.component.ts
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
.
.
export class SignupFormComponent implements OnInit {
    signupForm: FormGroup; // Declare the signupForm 

    //Inject the formbuilder into the constructor
	constructor(private fb:FormBuilder) {}
    
    ngOnInit() {
    
    ...
        
    }

}

而不是创建一个新的FormGroup() ,我们使用this.fb.group来构建一个表单。 除语法外,其他所有内容均保持不变。

app / signup-form / signup-form.component.ts
ngOnInit() {
        ...
        
		this.signupForm  = this.fb.group({
			email: ['',[Validators.required,
						Validators.pattern('[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$')]],
			password: this.fb.group({
				pwd: ['', [Validators.required, 
						   Validators.minLength(8)]],
				confirmPwd: ['', [Validators.required,
								  Validators.minLength(8)]]
			}),
			gender: ['', Validators.required],
			terms: ['', Validators.requiredTrue]
		})
}

显示验证错误

为了显示错误,我将在div元素上使用条件指令ngIf 。 让我们从电子邮件的输入控制字段开始:

<!-- Email error block -->
<div *ngIf="signupForm.controls.email.invalid && signupForm.controls.email.touched"
    Email is invalid
</div>

这里有几个问题。

  1. invalidpristine来自何处?
  2. signupForm.controls.email.invalid太长太深。
  3. 该错误没有明确说明为什么无效。

为了回答第一个问题,每个FormControl都具有某些属性,例如invalidvalidpristinedirtytoucheduntouched 。 我们可以使用它们来确定是否应该显示错误消息或警告。 下图详细描述了每个属性。

Angular反应方法中的FormControl属性

因此,仅当电子邮件无效时,才会显示带有*ngIf的div元素。 但是,即使在他们有机会编辑表单之前,也会给用户带来关于输入字段为空白的错误消息。

为了避免这种情况,我们添加了第二个条件。 仅在访问控件后才会显示错误。

为了摆脱方法名称的长链( signupForm.controls.email.invalid ),我将添加几个速记方法。 这使它们更易于访问和简短。

app / signup-form / signup-form.component.ts
export class SignupFormComponent implements OnInit {
...

    get email() { return this.signupForm.get('email'); }
    
	get password() { return this.signupForm.get('password'); }

	get gender() { return this.signupForm.get('gender'); }

	get terms() { return this.signupForm.get('terms'); }
    
}

为了使错误更明确,我在下面添加了嵌套的ngIf条件:

app / signup-form / signup-form.component.html
<!-- Email error block -->
	<div *ngIf="email.invalid && email.touched"
	 	class="col-sm-3 text-danger">

	 	<div *ngIf = "email.errors?.required">
	 		Email field can't be blank
	 	</div>

	 	<div *ngIf = "email.errors?.pattern">
	 		The email id doesn't seem right
	 	</div>

	 </div>

我们使用email.errors来检查所有可能的验证错误,然后以自定义消息的形式将其显示给用户。 现在,对其他表单元素执行相同的步骤。 这是我对密码和术语输入控件的验证进行编码的方式。

app / signup-form / signup-form.component.html
<!-- Password error block -->
       <div *ngIf="(password.invalid && password.touched)"
 		class="col-sm-3 text-danger">
 	
 		Password needs to be more than 8 characters
  	</div>
      
.
.
.
 <!--- Terms error block -->
   	  <div *ngIf="(terms.invalid && terms.touched)"
	 	class="col-sm-3 text-danger">
	 	
 		Please accept the Terms and conditions first.
   	  </div>
   	</div>

使用ngSubmit提交表单

我们几乎完成了表格。 它缺少我们即将实现的提交功能。

<form class="form-horizontal"  
    [formGroup]="signupForm" 
    (ngSubmit)="onFormSubmit()" >

在提交表单时,表单模型值应流入组件的用户属性。

app / signup-form / signup-form.component.ts
public onFormSubmit() {
    	if(this.signupForm.valid) {
			this.user = this.signupForm.value;
			console.log(this.user);
            /* Any API call logic via services goes here */
		}
	}

包起来

如果您从一开始就一直关注本教程系列,那么我们将对Angular中的两种流行的表单构建技术有实际经验。 模板驱动和模型驱动技术是实现同一目标的两种方法。 就我个人而言,出于以下原因,我更喜欢使用反应形式:

  • 所有表单验证逻辑都将位于组件类内部的单个位置。 与ngModel指令分散在整个模板中的模板方法相比,这种方法的生产率更高。
  • 与模板驱动的表单不同,模型驱动的表单更易于测试。 您不必借助端到端测试库来测试您的表单。
  • 验证逻辑将进入组件类而不是模板。
  • 对于具有大量表单元素的表单,此方法具有一个称为FormBuilder的东西,可以简化FormControl对象的创建。

我们错过了一件事,那就是写一个密码不匹配的验证器。 在本系列的最后一部分,我们将介绍在Angular中创建自定义验证器函数所需的所有知识。 敬请期待。

同时,有许多框架和库让您不忙,在Envato Market上有很多项目可供阅读,研究和使用。

翻译自: https://code.tutsplus.com/tutorials/introduction-to-forms-in-angular-4-reactive-forms--cms-29787

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值