# 3.2.对话框使用指南

在开发之中，常常会使用到对话框，创建对话框可以通过**服务方式**或**模板方式**创建。本小节中，我们来探讨一下。

## 一、服务方式创建（推荐使用）

### 关注点

使用对话框，人们常常关注的是以下问：

#### 父组件如何传递数据给对话框？

1. 在对话框中使用@Input() 装饰器声明对话框需要的参数
2. 父组件中，通过 nzComponentParams 传入参数

#### 对话框如何返回数据给父组件？

1. 在对话框中编写方法 ，方法返回值为待返回给父组件的数据
2. 在父组件中，通过modal.getContentComponent()或直接使用nzOnOk: (对话框组件)可以获取到对话框组件实例，通过调用步骤1中的方法获取返回值

### 示例

#### 父组件部分

**模板**

src/app/plan/plan.component.html

```markup
<a (click)="createEditModal(data)">更新</a>
```

**控制器**

src/app/plan/plan.component.ts

```typescript
import {Component, OnInit, ViewContainerRef} from '@angular/core';
import {NzModalService} from 'ng-zorro-antd/modal';
import {NzMessageService} from 'ng-zorro-antd';

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

  constructor(private service: PlanService,
              private modal: NzModalService,
              private message: NzMessageService,
              private viewContainerRef: ViewContainerRef) {
  }


  /**
   * 创建编辑对话框
   */
  createEditModal(plan: any): void {

    const modal = this.modal.create({
      nzTitle: '编辑计划',
      // 对话框的内容，本例中为一个表单组件
      nzContent: PlanUpdateComponent,
      nzViewContainerRef: this.viewContainerRef,
      // 父组件传递给对话框的数据
      nzComponentParams: {
        data: plan,
      },
      nzOnOk: (componentInstance: PlanUpdateComponent) => {
        // 校验表单的正确性，若表单校验通过，则获取表单数据并调用api执行更新操作；否则返回false，不允许提交表单
        const isValid: boolean = componentInstance.isValidForm();
        if (isValid) {
          const result: Plan = componentInstance.getFormData();
          this.updatePlan(result.id, result);
          return result;
        }
        return false;
      }
    });
    // 当对话框开启时，打印日志
    modal.afterOpen.subscribe(() => console.log('[afterOpen] emitted!'));
    // 当对话框关闭时，返回结果
    modal.afterClose.subscribe(result => console.log('[afterClose] The result is:', result));
  }

}
```

#### 对话框部分

**模板**

src/app/plan/plan-update/plan-update.component.html

```markup
<form nz-form [formGroup]="validateForm">

  <nz-form-item>
    <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="name" nzRequired>name</nz-form-label>
    <nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid name!">
      <input nz-input id="name" formControlName="name"/>
    </nz-form-control>
  </nz-form-item>

    ...

</form>
```

**控制器**

src/app/plan/plan-update/plan-update.component.ts

```typescript
import {Component, Input, OnInit} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {FormUtil} from '../../../utils/form.util';
import {NzModalRef} from 'ng-zorro-antd';

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

  // 对话框需要的参数，由外部组件传入
  @Input() data?: Plan;

  /**
   * 校验表单
   */
  isValidForm(): boolean {
    return this.formUtil.isValidForm(this.validateForm);
  }

  /**
   * 获取表单数据并进行处理
   */
  getFormData(): Plan {
    const data: Plan = this.formUtil.getFormData(this.data, this.validateForm);
    const times: any[] = this.validateForm.get('rangePicker').value;
    data.startTime = times[0];
    data.expireTime = times[1];
    return data;
  }

}
```

## 二、模板方式创建

### 关注点

使用对话框，人们常常关注的是以下问：

#### 父组件如何传递数据给对话框？对话框如何返回数据给父组件？

1. 在控制器中设置一个变量，作为父模板和对话框模板之间的桥梁
2. 在父模板或对话框模板中，改变变量的值，达到数据传递的作用

### 示例

#### 模板

```markup
<!-- 父模板部分 -->
<button nz-button nzType="primary" (click)="showUpdateModal()">


<!-- 对话框模板部分 -->
<nz-modal
  [(nzVisible)]="isUpdateModalVisible"
  nzTitle="Modal Title"
  (nzOnCancel)="handleUpdateCancel()"
  (nzOnOk)="handleUpdateOk()"
  [nzOkLoading]="isOkUpdateModalLoading>

  <form nz-form [formGroup]="validateForm">

    <nz-form-item>
      <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="name" nzRequired>name</nz-form-label>
      <nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid name!">
        <input nz-input id="name1" formControlName="name" [ngModel]="waitingUpdatedData?.name"/>
      </nz-form-control>
    </nz-form-item>

    ...

</nz-modal>
```

#### 控制器

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

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

  // 控制对话框是否可见
  isUpdateModalVisible = false;

  // 控制对话框
  isOkUpdateModalLoading = false;

  // 对话框和父组件之间的桥梁，用于传递数据
  waitingUpdatedData: any;

  validateForm!: FormGroup;

  constructor(private fb: FormBuilder) {
  }

  ngOnInit(): void {
    this.validateForm = this.fb.group({
      name: [null, [Validators.required]],
      expectedValue: [null, [Validators.required]],
      rangePicker: [[]],
      description: [null, [Validators.required]],
    });
    ...
  }

  /**
   * 显示对话框
   */
  showUpdateModal(id: string, data: any): void {
    // 初始化对话框所需的数据
    const dateRange: Date[] = [new Date(data.startTime), new Date(data.expireTime)];
    data['dateRange'] = dateRange;
    this.waitingUpdatedData = data;
    console.log(this.waitingUpdatedData)
    // 手动设置对话框可见
    this.isUpdateModalVisible = true;
  }

  /**
   * 表单提交
   */
  submitUpdateForm(): void {
    this.waitingUpdatedData.startTime = this.waitingUpdatedData.dateRange[0];
    this.waitingUpdatedData.expireTime = this.waitingUpdatedData.dateRange[1];
    this.updatePlan(this.waitingUpdatedData.id, this.waitingUpdatedData);
  }

  /**
   * 重置表单
   */
  resetForm(): void {
    this.validateForm.reset();
    for (const key in this.validateForm.controls) {
      this.validateForm.controls[key].markAsPristine();
      this.validateForm.controls[key].updateValueAndValidity();
    }
  }

  /**
   * 处理ok时间
   */
  handleUpdateOk(): void {
    // 显示加载中
    this.isOkUpdateModalLoading = true;
    // 等待1秒，用于模拟请求api所需时间。 1秒后，提交表单数据，手动隐藏对话框，并重置表单
    setTimeout(() => {
      this.submitUpdateForm();
      this.isUpdateModalVisible = false;
      this.isOkUpdateModalLoading = false;
      this.resetForm();
    }, 1000);
  }

  /**
   * 处理取消事件
   */
  handleUpdateCancel(): void {
    // 设置对话框不可见，并重置表单
    this.isUpdateModalVisible = false;
    this.resetForm();
  }

}
```

## 三、对比

相比服务方式，模板方式创建对话框代码不够优雅

* 服务方式创建对话框只需要一个方法，而模板方式却需要1-2个变量、4-5个方法
* 模板方式需要在控制器中，手动通过变量控制对话框的显示和消失
* 模板方式需要手动重置表单
