首页 Angular Angular 学习之路 21 – Todo App (5)

Angular 学习之路 21 – Todo App (5)

0 1.6K

上一章我们实现了 Mark all as completed 选择框的相关操作。接下来,我们要实现的是另外一个需求:

计数器

以复数形式显示当前有效的待办。将数字放在<strong>标签中。同时需要正确显示item的复数形式:0 items1 item2 items。例如,2 items left。

很明显,计数器的值应该根据todoService.#todoList数组计算而来。那么,我们就在TodoService里面增加一个属性:

...
  get uncompletedCount(): number {
    return this.#todoList.filter(it => !it.completed).length;
  }
...

这里,我们使用了 TypeScript 的 getter 函数,返回#todoList中所有completed属性为false的待办事项的个数。

然后我们修改TodoListComponent的模板:

...
  <!-- This should be `0 items left` by default -->
  <span class="todo-count">
    <strong>{{ todoService.uncompletedCount }}</strong> {{ todoService.uncompletedCount === 1 ? 'item' : 'items' }} left
  </span>
...

我们使用插值将todoService.uncompletedCount显示出来,后面的items则要根据todoService.uncompletedCount的值判断显示为item还是items

到目前为止,一切都很顺利。

下面,我们简单修改一下uncompletedCount的实现:

...
  get uncompletedCount(): number {
    console.log('uncompletedCount');
    return this.#todoList.filter(it => !it.completed).length;
  }
...

这么做的目的是,我们要看这个 getter 函数究竟调用了多少次。重新编译运行一下页面就会发现,这个函数每次都会调用很多次。即便我们并没有修改待办事项的状态,uncompletedCount也会被调用。

这是由于当某些事件发生时,Angular 会对页面做变更检测,比如鼠标点击事件。可以看到,即便我们在页面随意点击鼠标,函数都会被调用。因此,不应该在模板中进行任何函数调用,特别是消耗非常巨大的函数调用。这会直接导致页面响应变慢。

为解决这一问题,我们要将uncompletedCount修改为普通变量,然后与之前的allCompleted类似,在数组有变化时,同时要修改uncompletedCount的值。

export class TodoService {

  allCompleted = false;

  uncompletedCount = 0;

  get todoList(): Todo[] {
    return this.#todoList;
  }

  #todoList: Todo[] = [];

  constructor() { }

  addTodo(todo: Omit<Todo, 'id'>): void {
    this.#todoList.push({
      id: this.#todoList.length,
      ...todo
    });
    this.allCompleted = false;
    this.uncompletedCount++;
  }

  deleteTodo(todo: Todo): void {
    this.#todoList = this.#todoList.filter(it => it.id !== todo.id);
    this.allCompleted = this.#todoList.filter(it => !it.completed).length === 0;
    this.uncompletedCount = this.#todoList.filter(it => !it.completed).length;
  }

  toggleTodo(completed: boolean, todo?: Todo): void {
    if (!todo) {
      this.#todoList.forEach(it => it.completed = completed);
      this.allCompleted = completed;
    } else {
      todo.completed = completed;
      this.allCompleted = this.#todoList.filter(it => !it.completed).length === 0;
    }
    this.uncompletedCount = this.#todoList.filter(it => !it.completed).length;
  }

}

这样,页面模板绑定的是一个变量,而不是函数,就不会引起调用的问题了。

下面我们来看另外一个需求:

Clear completed 按钮

点击之后移除已完成的待办。如果没有已完成的待办,隐藏该按钮。

Clear completed 按钮在TodoListComponent组件,因此,我们首先修改TodoListComponent的模板:

...
  <!-- Hidden if no completed items are left ↓ -->
  <button *ngIf="todoService.uncompletedCount !== todoService.todoList.length"
          (click)="clearCompletedTodos()"
          class="clear-completed">
    Clear completed
  </button>
...

隐藏按钮使用todoService.uncompletedCount !== todoService.todoList.length进行判断。如果未完成总数与所有待办事项的总数不同,则说明有已完成的项目,此时需要显示按钮。然后需要给按钮的click事件回调:

...
  clearCompletedTodos(): void {
    this.todoService.clearCompletedTodos();
  }
...

最主要的是todoService.clearCompletedTodos()的实现。

...
  clearCompletedTodos(): void {
    this.#todoList = this.#todoList.filter(it => !it.completed);
    this.allCompleted = false;
    this.uncompletedCount = this.#todoList.length;
  }
...

这个实现并不复杂:#todoList应该保留所有未完成的待办事项,然后要记得修改allCompleteduncompletedCount的值。

本章完整代码:https://files.devbean.net/code/todomvc-app-5.zip

发表评论

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2