第四课:用户输入和表单

这节课我们来看Camp Details和My Details页的功能,也就是这些功能组成我们的输入功能。如果你已经完成了本书的其他应用的话,或者之前用过Ionic 2的输入域,你可能有这样用过ngModel

  1. <ion-input type="text" [(ngModel)]="myField"></ion-input>

这种写法给输入域设置了双向数据绑定,这些我在基础部分详细讨论过,本质上他就是他这个输入域的值捆绑到本页类定义的:

  1. this.myField

如果你改变this.myField的值的话,将会映射到这个输入框来,如果输入框的值改变的话将会被映射到this.myField。对于管理用户输入来将,这是个非常简单的方法,但是如果有更多更复杂的输入域的时候,那么就需要使用FormBuilder了。
通过使用From Builder,不仅可以使代码更漂亮,还可以在“控制”每个输入框的同时带来更多强大的功能(如果看一眼Giflists应用的话,会看到我们用来控制文本域改变的定语,通过他可以做很多神奇的事情)。
我们也可以使用Validators与Form Builder写作,他允许我们捆绑一个“验证”到指定的输入域,这个验证将会检查输入是否允许(即,是否是正确的邮件格式)。
我们直接进入实现过程吧,更直观些。我们会先实现Camp Details页面的功能,然后烤制到My Details页。
> 修改 src/pages/camp=details/camp-details.html 的列表部分为如下:

  1. <ion-list no-lines>
  2. <form [formGroup]="campDetailsForm" (change)="saveForm()">
  3. <ion-item>
  4. <ion-label stacked>Gate Access Code</ion-label>
  5. <ion-input formControlName="gateAccessCode" type="text"></ion-input>
  6. </ion-item>
  7. <ion-item>
  8. <ion-label stacked>Ammenities Code</ion-label>
  9. <ion-input formControlName="ammenitiesCode" type="text"></ion-input>
  10. </ion-item>
  11. <ion-item>
  12. <ion-label stacked>WiFi Password</ion-label>
  13. <ion-input formControlName="wifiPassword" type="text"></ion-input>
  14. </ion-item>
  15. <ion-item>
  16. <ion-label stacked>Phone Number</ion-label>
  17. <ion-input formControlName="phoneNumber" type="text"></ion-input>
  18. </ion-item>
  19. <ion-item>
  20. <ion-label stacked>Departure Date</ion-label>
  21. <ion-datetime formControlName="departure" displayFormat="DD/MM/YYYY"></ion-datetime>
  22. </ion-item>
  23. <ion-item>
  24. <ion-label stacked>Notes</ion-label>
  25. <ion-textarea formControlName="notes" type="text"></ion-textarea>
  26. </ion-item>
  27. </form>
  28. </ion-list>

这里的输入框都差不多只是有一些比较重要的区别。首先,我们将它们包装到一个form标签中:

  1. <form [formGroup]="campDetailsForm" (change)="saveForm()">

我们定义了formGroup的值为campDetailsForm,这个值很快就会跟Form Builder一起使用。同时,我们也监听了(change)事件并在检测到的时候触发saveForm函数,意思就是用户改变和切换输入框的时候都会触发saveForm函数,但是不会在输入单个字符的时候触发(我们想要的话也可以做到)。通常对于表单而言我们都是监听他的(submit)事件并在其中处理数据,但是我们不想用户点击“Save”按钮或者其他类似操作来处理,我们只是希望用户在输入了一个新值的时候尽快存储起来。
另一个重要的变更时我们给每个输入框添加了formControlName,并给他指定了一个名字(跟我们将要使用ngModel做的差不多)。再次,我们就快把他和Form Builder一起使用了。
现在,我们看一下类定义。首先,我们改一下构造器:
> 修改 src/pages/camp-details/camp-details.ts 的构造器如下:

  1. campDetailsForm: FormGroup;
  2. constructor(public navCtrl: NavController, public formBuilder: FormBuilder,public dataService: Data) {
  3. this.campDetailsForm = formBuilder.group({
  4. gateAccessCode: [''],
  5. ammenitiesCode: [''],
  6. wifiPassword: [''],
  7. phoneNumber: [''],
  8. departure: [''],
  9. notes: ['']
  10. });
  11. }

由于我们在模板中已经将formGroup定义为campDetailsForm,我们这里可以指定一个新的Form Builder组。我们通过将之前指定给输入框的formControlName提供进来创建一个新的组。注意,我们提供了一个包含了空白字符串的数组,他用于作为输入框的初始值,例如:

  1. gateAccessCode: ['54321']

这段代码会将dateAccessCode输入框的初始值设为‘54321’。你也可以像这样在这里提供一个验证器(validator):

  1. gateAccessCode: ['', Validators.required]

以上代码会把gateAccessCode变为必需的域。这就是设置表单的全部内容了,现在我们只要实现saveForm函数来。
> 修改 src/pages/camp-details/camp-details.ts 的 saveForm 函数:

  1. saveForm(): void {
  2. let data = this.campDetailsForm.value;
  3. //this.dataService.setCampDetails(data);
  4. }

注意:我们注释掉了对数据服务的调用时因为我们目前没有实现他,不然的话TypeScript会向我们抛出错误。
现在,我们可以在任何时候通过this.campDetailsForm.value获取表单的值。他将会返回一个对象包含了所有的值,也就是我们需要保存的数据。所以我们将这些数据传给数据服务去保存(现在还未实现)。
记住,saveForm函数在任何输入框改变的时候会触发,所以任何他们改变的时候我们读取这些值并保存他们。
现在我们只需要把这些变更映射到我们的My Details页。这些基本上一样的,我们我直接就复制粘贴了。不解释。
> 修改 src/pages/my-details/my-details.html 为如下:

  1. <ion-header>
  2. <ion-navbar color="primary">
  3. <ion-title>
  4. <img src = "assets/images/logo.png" class="logo" />
  5. </ion-title>
  6. </ion-navbar>
  7. </ion-header>
  8. <ion-content padding>
  9. <ion-card>
  10. <ion-card-header>
  11. My Details
  12. </ion-card-header>
  13. <ion-card-content>
  14. Update this form with your details so you have an easy reference for
  15. later.
  16. </ion-card-content>
  17. </ion-card>
  18. <ion-list no-lines>
  19. <form [formGroup]="myDetailsForm" (change)="saveForm()">
  20. <ion-item>
  21. <ion-label stacked>Car Registration</ion-label>
  22. <ion-input formControlName="carRegistration" type="text"></ion-input>
  23. </ion-item>
  24. <ion-item>
  25. <ion-label stacked>Trailer Registration</ion-label>
  26. <ion-input formControlName="trailerRegistration" type="text"></ion-input>
  27. </ion-item>
  28. <ion-item>
  29. <ion-label stacked>Trailer Dimensions</ion-label>
  30. <ion-input formControlName="trailerDimensions" type="text"></ion-input>
  31. </ion-item>
  32. <ion-item>
  33. <ion-label stacked>Phone Number</ion-label>
  34. <ion-input formControlName="phoneNumber" type="text"></ion-input>
  35. </ion-item>
  36. <ion-item>
  37. <ion-label stacked>Notes</ion-label>
  38. <ion-textarea formControlName="notes" type="text"></ion-textarea>
  39. </ion-item>
  40. </form>
  41. </ion-list>
  42. </ion-content>

> 修改 src/pages/my-details/my-details.ts的构造器如下:

  1. myDetailsForm: FormGroup;
  2. constructor(public navCtrl: NavController, public formBuilder: FormBuilder,public dataService: Data) {
  3. this.myDetailsForm = formBuilder.group({
  4. carRegistration: [''],
  5. trailerRegistration: [''],
  6. trailerDimensions: [''],
  7. phoneNumber: [''],
  8. notes: ['']
  9. });
  10. }

> 修改 src/pages/my-details/my-details.ts的 saveForm函数如下:

  1. saveForm(): void {
  2. let data = this.myDetailsForm.value;
  3. //this.dataService.setMyDetails(data);
  4. }

确实,表单不是世界上最刺激的东西(最起码大部分人会这么认为),但是对于移动应用来讲他是极度重要的组件之一,所以了解他们的工作方式非常重要。学会使用Form Builder可以让你的表单更好管理更强大,但是有时候,一个简单的[(ngModel)]就足够了。
在下一节课,我们将学习稍有有趣一点的东西,也稍微复杂一点:我们来实现Google Maps!