迈向angularjs2系列(7):表单
目录
一:校验表单的使用
1.搭建脚手架
2.校验表单的使用
3.select下拉列表的用法
一: 校验表单的使用
对于CRUD型的应用,表单是必备组件。
1.搭建脚手架
git clone https://github.com/mgechev/switching-to-angular2.git form1
npm install
npm start
app目录删掉所有内容后,新建form目录,form目录创建index.html和app.ts
index.html是默认内容。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title><%= TITLE %></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css"> <!-- inject:css --> <!-- endinject --> </head> <body> <app>Loading...</app> <!-- inject:js --> <!-- endinject --> <%= INIT %> </body> </html>
app.ts只是简单的App组件和启动函数。
import {Component} from '@angular/core'; import {bootstrap} from '@angular/platform-browser-dynamic'; @Component({ selector: 'app', template: ` <div>初始状态</div> ` }) class App {} bootstrap(App);
打开http://localhost:5555/dist/dev/form/,可看见预先填写的字符串。
准备就绪!
2.校验表单的使用
分为3个部分:
第一部分:表单指令集和PROVIDERS引入,以及装饰器准备
import{FORM_DIRECTIVES,FORM_PROVIDERS} from '@angular/common';
FORM_DIRECTIVES是表单指令集,
FORM_PROVIDERS是内置的PROVIDERS数组。
组件要使用导入的内容,那么装饰器就要提供相应的属性。
@Component({ selector: 'app', templateUrl:"./app.html", styles:[], directives:[FORM_DIRECTIVES], providers:[FORM_PROVIDERS] })
第二部分:form标签使用ngForm指令
<form #f="ngForm" (ngSubmit)="login()"> <!--名称为f的表单,可用来引用当前form--> </form>
当angular2发现这样的form标签,而且含有ngForm指令,就会把它当做form指令来增强了。 (ngSubmit)="login()" 当然是提交咯。
第三部分:对input等进行表单校验
<form #f="ngForm" (ngSubmit)="login()"> <input id="nameInput" type="text" required ngControl="nameInput" minlength="6" maxlength="11"/> </form>
这里使用的ngControl指令会在表单的值发生改变时进行校验,同时为校验的不同阶段添加class类,还会扩展required属性的语义,
form不同的class类:
ng-untouched。控件还没有被访问过。
ng-touched。控件已经被访问过。
ng-pristine。控件的值没有被修改。
ng-dirty。控件的值已经被修改过。
ng-valid。控件上所绑定的所有校验器都返回了true。
ng-invalid。控件上所绑定的某个校验器返回了false。
比如
app.html:
<form #f="ngForm" (ngSubmit)="login()"> <lable>用户名:</lable> <br/> <input id="nameInput" type="text" required ngControl="name" minlength="11" maxlength="11"/> <br/> <span>调试打印:{{f.valid}}</span> </form>
app.ts:
import {Component} from '@angular/core'; import {bootstrap} from '@angular/platform-browser-dynamic'; import{FORM_DIRECTIVES,FORM_PROVIDERS} from '@angular/common'; @Component({ selector: 'app', templateUrl:"./app.html", styles: [ `input.ng-dirty.ng-invalid{ border:1px solid red; //边框 }` ], directives:[FORM_DIRECTIVES], providers:[FORM_PROVIDERS] }) class App {} bootstrap(App);
由于输入不合法,会会有红色边框
3.select下拉列表的用法
我们以组件的方式来开发这个select下拉列表。
首先,组件代码都准备好。
app新建form-select目录,目录下创建app.html、app.ts、index.html。
类似
index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title><%= TITLE %></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css"> <!-- inject:css --> <!-- endinject --> </head> <body> <app>Loading...</app> <!-- inject:js --> <!-- endinject --> <%= INIT %> </body> </html>
注意,里面有boostrap引入,所以可以直接的使用bootstrap的样式哦。
app.html:
import {Component} from '@angular/core'; import {bootstrap} from '@angular/platform-browser-dynamic'; import{FORM_DIRECTIVES,FORM_PROVIDERS} from '@angular/common'; @Component({ selector: 'app', templateUrl:"./app.html", styles: [ `select.ng-dirty.ng-invalid{ border:1px solid red; }` //如果不合法会有红色的边框 ], directives:[FORM_DIRECTIVES], providers:[FORM_PROVIDERS] }) class App { youLike:string[]=[ "艾莎","安娜","汉斯","阿伦黛尔" ] //冰雪奇缘的角色 } bootstrap(App);
首先使用ngFor指令把youLike数组遍历到option选项中。
<select class="form-control"> <option *ngFor="#yL of youLike" [value]="yL"> {{yL}} </option> </select>
然后使用ngControl指令进行校验,添加required属性、使用ngModel获得选择项。
<select class="form-control" required ngControl="youLike"[(ngModel)]="youLike"> <option *ngFor="#yL of youLikes" [value]="yL"> {{yL}} </option> </select> <span>调试:youLike选择项是{{youLike}}</span>
浏览器显示结果
二: 表单校验详解
1.自定义控件的校验器
虽然angular2提供了一组预定义好的校验器,但是并不能覆盖各种各样的格式。这里我们自定义一个email校验器。
Step1:写好校验函数
校验函数接收value值为参数,没有值或者不匹配,返回null,否则返回{"invalidEmail":true}。
function validateEmail(emailControl){ var emailReg=/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/; //正则 if(!emailControl||emailReg.test(emailControl.value)){ return null; }else{ return {"invalidEmail":true} } }
step2:把校验函数包装到指令中。
import {NG_VALIDATORS} from "@angular/common"; import {Directive} from "@angular/core"; //引入NG_VALIDATORS和Directive @Directive({ selector:"[email-input]", //校验指令定义成属性 providers: [{ provide: NG_VALIDATORS, useValue: validateEmail, multi: true }] //使用NG_VALIDATORS定义了单个provider,我们注射了绑定的值。 }) class EmailValidator{ }
step3:email控件上增加email-input属性。
<input id="emailInput" class="form-control" email-input type="text" ngControl="email" [(ngModel)]="email"/>
step4:到组件上挂载email-input指令。
@Component({ selector: 'app', templateUrl:"./app.html", styles: [ `input.ng-dirty.ng-invalid{ border:1px solid red; }` ], directives:[FORM_DIRECTIVES,EmailValidator], //EmailValidator指令添加到指令选项,就可以使用了 providers:[FORM_PROVIDERS] }) class App {}
打开http://localhost:5555/dist/dev/email-validate/
完整的app.ts代码:
ch7-forms/form1/app/email-validate/app.ts:
import {Component} from '@angular/core'; import {bootstrap} from '@angular/platform-browser-dynamic'; import{FORM_DIRECTIVES,FORM_PROVIDERS} from '@angular/common'; import {NG_VALIDATORS} from "@angular/common"; import {Directive} from "@angular/core"; //引入NG_VALIDATORS和Directive function validateEmail(emailControl){ var emailReg=/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/; if(!emailControl||emailReg.test(emailControl.value)){ return null; }else{ return {"invalidEmail":true} } } @Directive({ selector:"[email-input]", //校验指令定义成属性 providers: [{ provide: NG_VALIDATORS, useValue: validateEmail, multi: true }] //使用NG_VALIDATORS定义了单个provider,我们注射了绑定的值。 }) class EmailValidator{ } @Component({ selector: 'app', templateUrl:"./app.html", styles: [ `input.ng-dirty.ng-invalid{ border:1px solid red; }` ], directives:[FORM_DIRECTIVES,EmailValidator], providers:[FORM_PROVIDERS] }) class App {} bootstrap(App);
2.ngForm指令
新建ng-form目录,目录下创建index.html、app.ts、app.html。
index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title><%= TITLE %></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css"> <!-- inject:css --> <!-- endinject --> </head> <body> <app>Loading...</app> <!-- inject:js --> <!-- endinject --> <%= INIT %> </body> </html>
app.html暂时还使用之前的form代码:
<form #f="ngForm" (ngSubmit)="login()"> <lable>用户名:</lable> <br/> <input id="nameInput" type="text" required ngControl="name" minlength="11" maxlength="11"/> <br/> <span>调试打印:{{f.valid}}</span> </form>
app.ts暂时使用之前的代码:
import {Component} from '@angular/core'; import {bootstrap} from '@angular/platform-browser-dynamic'; import{FORM_DIRECTIVES,FORM_PROVIDERS} from '@angular/common'; @Component({ selector: 'app', templateUrl:"./app.html", styles: [ `input.ng-dirty.ng-invalid{ border:1px solid red; }` ], directives:[FORM_DIRECTIVES], providers:[FORM_PROVIDERS] }) class App {} bootstrap(App);
打开http://localhost:5555/dist/dev/ng-form/,会有如下结果
现在为了演示form元素的用法,定义control-error组件,用来显示报错信息。
step1:定义ControlErrors组件
import{NgControl,NgForm} from "@angular/common"; import{Host} from "@angular/core" //NgControl类是一个抽象类,用来代表angular表单 //Host是一个装饰器,与依赖注入有关 @Component({ template: `<div>{{currentError}}</div>`, selector: 'control-errors', inputs: ['control', 'errors'] }) //组件的输入是control和errors class ControlErrors { errors: Object; control: string; constructor(@Host() private formDir: NgForm) {} get currentError() { let control = this.formDir.controls[this.control]; let errorMessages = []; if (control && control.touched) { errorMessages = Object.keys(this.errors) .map(k => control.hasError(k) ? this.errors[k] : null) .filter(error => !!error); } return errorMessages.pop(); } }
step2:组件注册
@Component({ selector: 'app', templateUrl:"./app.html", styles: [ `input.ng-dirty.ng-invalid{ border:1px solid red; }` ], directives:[FORM_DIRECTIVES,ControlErrors], //组件注册 providers:[FORM_PROVIDERS ] }) class App { }
step3:使用control-errors组件
<form #f="ngForm" > <div class="form-group"> <label class="control-label" for="realNameInput">Real name</label> <div> <input id="realNameInput" class="form-control" type="text" ngControl="name" required maxlength="5"> <control-errors control="name" [errors]="{ 'required': 'Real name is required', 'maxlength': 'The maximum length of the real name is 5characters' }"></control-errors> </div> </div> </form>
打开http://localhost:5555/dist/dev/ng-form/,使得input框获得焦点,然后失去焦点,浏览器结果如图
OK!
最终完整的代码:
app.html:
<form #f="ngForm" > <div class="form-group"> <label class="control-label" for="realNameInput">Real name</label> <div> <input id="realNameInput" class="form-control" type="text" ngControl="name" required maxlength="5"> <control-errors control="name" [errors]="{ 'required': 'Real name is required', 'maxlength': 'The maximum length of the real name is 5characters' }"></control-errors> </div> </div> </form>
app.ts:
import {Component} from '@angular/core'; import {bootstrap} from '@angular/platform-browser-dynamic'; import{FORM_DIRECTIVES,FORM_PROVIDERS} from '@angular/common'; @Component({ selector: 'app', templateUrl:"./app.html", styles: [ `input.ng-dirty.ng-invalid{ border:1px solid red; }` ], directives:[FORM_DIRECTIVES], providers:[FORM_PROVIDERS] }) class App {} bootstrap(App);
index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title><%= TITLE %></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css"> <!-- inject:css --> <!-- endinject --> </head> <body> <app>Loading...</app> <!-- inject:js --> <!-- endinject --> <%= INIT %> </body> </html>
3.双向数据绑定
ngModel指令轻松实现双向数据绑定。选择器为[(ngModel)]。
目录结构
ch7-forms/form1/app/ng-model/app.ts:
import {Component} from '@angular/core'; import {bootstrap} from '@angular/platform-browser-dynamic'; import{NgModel} from "@angular/common"; @Component({ selector: 'app', templateUrl:"./app.html", directives:[NgModel] }) class App { name:string; } bootstrap(App); //非常简单的例子
ch7-forms/form1/app/ng-model/app.html:
<input type="text" [(ngModel)]="name"/> <div>{{name}}</div>
ch7-forms/form1/app/ng-model/index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title><%= TITLE %></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css"> <!-- inject:css --> <!-- endinject --> </head> <body> <app>Loading...</app> <!-- inject:js --> <!-- endinject --> <%= INIT %> </body> </html>
打开http://localhost:5555/dist/dev/ng-model/,浏览器结果如图,当修改了input的值,label会自动刷新。
三: 表单数据的存储
表单数据很多,并非一两个,我们可以通过把数据以属性的形式挂载到某个对象(实例上),做非常简洁的存储。
只做一个简单的例子:
ch7-forms/form1/app/user/app.html:
<input type="text" [(ngModel)]="user.name"/> <button (click)="getUser(user)">获得user对象</button>
ch7-forms/form1/app/user/user.ts:
export class User{ name:string; } //数据从logic分离出来了 //实际情况会有更多的属性
ch7-forms/form1/app/user/app.ts:
//顶级组件的代码 import {Component} from '@angular/core'; import {bootstrap} from '@angular/platform-browser-dynamic'; import{NgModel} from "@angular/common"; import{User}from "./user"; @Component({ selector: 'app', templateUrl:"./app.html", directives:[NgModel] }) class App { user=new User(); getUser(user){ console.log(user) } } bootstrap(App);
ch7-forms/form1/app/user/index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title><%= TITLE %></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css"> <!-- inject:css --> <!-- endinject --> </head> <body> <app>Loading...</app> <!-- inject:js --> <!-- endinject --> <%= INIT %> </body> </html>
当input框输入值之后,会打印对象
另外关于提交有一个小方法阻止重复提交。
<form #f="ngForm" (ngSubmit)="login()" [hidden]="submitted"> </form>
一开始设置submitted为false,那么表单就可以通过login来提交了,而一旦提交完成,设置为true,那么就隐藏起form,也就不会有再次提交了。妙!