angular 指令渲染_[Angular 组件库 NG-ZORRO 基础入门] - 待办事项 + 双向绑定

前言回顾

这几天我们已经完成了 TODO 待办事项 的一些基本功能,涉及多个组件的使用方式,今天我们将 TODO 待办事项 的一些组件独立出来维护,介绍一些组件设计的小方法后,我们将对这个项目里涉及的组件进行一一讲解,有助于大家更加深刻地理解 NG-ZORRO 的常用组件。

看一下我们目前的项目情况:

20219e5d83ddfd96d219413173a1f2e4.png

待办事项

菜单目录

todo
├── task-detail
│   ├── task-detail.component.html
│   ├── task-detail.component.less
│   └── task-detail.component.ts
├── todo.component.html
├── todo.component.less
└── todo.component.ts

我们看到,待办事项 项目文件结构如图,我们现在只将 task 详情抽离出来,看一下 todo.component.html 里渲染待办任务列表的代码,这里显示逻辑全部写在该 component 里面,对于后续维护十分困难:

<div class="task-container">
  <div class="task-todo">
    <nz-table [nzData]="listOfTodoTasks" [nzNoResult]="noResultTpl" [nzFrontPagination]="false" [nzShowPagination]="false" [nzWidthConfig]="tableWidthConfig">
      <tbody>
        <tr *ngFor="let task of listOfTodoTasks">
          <td
            nzShowCheckbox
            (nzCheckedChange)="checkTask(task)"
          ></td>
          <td>{{task.name}}</td>
          <td>
            <i class="more-actions" nz-icon nzType="ellipsis" nz-dropdown [nzDropdownMenu]="actions" nzPlacement="bottomRight" nzTrigger="click" (click)="setActivatedTask(task)"></i>
          </td>
        </tr>
      </tbody>
    </nz-table>
  </div>
</div>

列表组件

那么我们想剥离列表部分,作为一个独立模块来维护该怎么做呢?

创建组件

很简单,让我们先创建一个组件 task-list,然后看下我们新的项目结构:

$ cd ng-zorro-ironman2020
$ ng g c components/demos/todo/task-list --skip-import

看一下新目录结构

todo
├── task-detail
│   ├── task-detail.component.html
│   ├── task-detail.component.less
│   └── task-detail.component.ts
├── task-list
│   ├── task-list.component.html
│   ├── task-list.component.less
│   └── task-list.component.ts
├── todo.component.html
├── todo.component.less
└── todo.component.ts

组件设计

我们把列表相关的代码全部迁移至 TaskListComponent,这时我们面临一个问题,如何渲染待办任务数据并和 TodoComponent 内的数据同步。

我们先来第一种设计方案(stackblitz 在线代码演示):

@Component({
  selector       : 'app-task-list',
  templateUrl    : './task-list.component.html',
  styleUrls      : [ './task-list.component.less' ],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TaskListComponent implements OnInit, ControlValueAccessor {
  @Input() listOfTasks: ITask[] = [];
  constructor() {}
  ngOnInit() {}
}

task-list.component.html 使用方式为:

<app-task-list [listOfTasks]="listOfTodoTasks"></app-task-list>

todo.component.ts 新增任务方法仍然不变:

addTask(): void {
  if (this.createForm.valid) {
    const newTask = {
      ...this.createForm.getRawValue(),
      id: new Date().getTime()
    };
    this.listOfTodoTasks = this.listOfTodoTasks.concat([ newTask ]);
    // reset after adding new task
    this.createForm.get('name').reset();
  }
}

我们接受一个 ITask 类型的数组,然后渲染需要的数据,咋一看并没有什么问题,但是当尝试去新增一个新数据的时候出现了问题,我们看一下:

发现问题了吗?我们在 TaskListComponent 组件中完成的任务又被添加回来了,原因很简单,就是 TodoComponentlistOfTodoTasks 数据和 TaskListComponentlistOfTasks 数据不是同步的,我们可以通过 双向绑定 的方法来实现数据同步(对于通过 API 方式请求数据渲染的场景可以通过 Rxjs 的 Subject 去订阅重新渲染,我们暂时不对这种情况深入讨论)。

双向绑定

了解双向绑定

对于上面的例子,如果我们要以 双向绑定 模式来使用的话,该怎么写呢?

<app-task-list [(ngModel)]="listOfTodoTasks"></app-task-list>

那么到底什么是 ngModel 呢?看一下 官方介绍,当然,要想使用 ngModel,别忘了引入 FormsModule

它可以接受一个领域模型作为可选的 Input。如果使用 [] 语法来单向绑定到 ngModel,那么在组件类中修改领域模型将会更新视图中的值。 如果使用 [()] 语法来双向绑定到 ngModel,那么视图中值的变化会随时同步回组件类中的领域模型。

如何在自定义组件中实现双向绑定

在官方文档 模板语法 一节中,专门提到了 ngModel 的实现过程。

ngModel 输入属性会设置该元素的值,并通过 ngModelChange 的输出属性来监听元素值的变化。
各种元素都有很多特有的处理细节,因此 NgModel 指令只支持实现了ControlValueAccessor的元素, 它们能让元素适配本协议。 输入框正是其中之一。 Angular 为所有的基础 HTML 表单都提供了值访问器(Value accessor),表单一章展示了如何绑定它们。
你不能把 [(ngModel)] 用到非表单类的原生元素或第三方自定义组件上,除非写一个合适的值访问器,这种技巧超出了本章的范围。
你自己写的 Angular 组件不需要值访问器,因为你可以让值和事件的属性名适应 Angular 基本的双向绑定语法,而不使用 NgModel。 前面看过的 sizer就是使用这种技巧的例子。

文档中提到“NgModel 指令只支持实现了ControlValueAccessor的元素”,很显然我们的组件需要继承 ControlValueAccessor,看到以下定义,我们只需要继承需要的功能,注册 NG_VALUE_ACCESSOR 即可实现。

interface ControlValueAccessor {
  writeValue(obj: any): void
  registerOnChange(fn: any): void
  registerOnTouched(fn: any): void
  setDisabledState(isDisabled: boolean)?: void
}

开始改造

让我们改造一下 TaskListComponent,打开 task-list.component.ts,重写如下代码:

import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
  selector: 'app-task-list',
  templateUrl: './task-list.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
  // register NG_VALUE_ACCESSOR to support ngModel
  providers      : [
    {
      provide    : NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => TaskListComponent),
      multi      : true
    }
  ]
})
export class TaskListComponent implements OnInit {
  // 部分代码已省略
  listOfTasks: ITask[] = [];
  constructor(
    private cdr: ChangeDetectorRef,
  ) {}
  ngOnInit() {}

  checkTask(task: ITask): void {
    this.listOfTasks = this.listOfTasks.filter(v => v.id !== task.id);
    // ngModelChange事件,同步数据
    this.onChange(this.listOfTasks);
  }

  /**
   * Update ngModel -> update listOfSelectedValue
   */
  onChange: (value: ITask[]) => void = () => [];
  onTouched: () => void = () => null;

  writeValue(tasks: ITask[]): void {
    if (tasks) {
      this.listOfTasks = [ ...tasks ];
      // markForCheck to render table data
      this.cdr.markForCheck();
    }
  }

  registerOnChange(fn: (value: ITask[]) => void): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }
}

这样,一个支持 ngModel 双向绑定的组件已经完成了,我们再来看看 这个例子 ,现在已经能正常渲染数据了:

总结 & 预告

今天我们介绍了如何通过 implements ControlValueAccessor来实现自定义组件的双向绑定,这对于一些表单业务场景有很大的作用,能够保证我们同一份数据在多组件模块下的同步问题。

之前在 待办事项 项目中,很多组件都是使用了最简单常用的使用方式和属性,我们在接下来几天会对这个项目中涉及的组件进行专项解读,帮助大家更容易地理解怎么使用这些组件。

相关资源

  • iThelp 地址:
[Angular 元件庫 NG-ZORRO 基礎入門] Day 06 - 待辦事項 + 雙向繫結 - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天​ithelp.ithome.com.tw
6ace644430bb75823faa62770cd56b48.png
  • github 代码:
simplejason/ng-zorro-ironman2020​github.com
de6f5a82589f613c07f6dedbe3ccf609.png
  • stackblitz 在线示例:
angular-s3c1qh-qplzgm - StackBlitz​stackblitz.com
  • ngModel:
Angular - NgModel​angular.cn
  • ControlValueAccessor:
Angular - ControlValueAccessor​angular.cn
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值