4. Todo一覧と消化状況を表示する

July 18, 2016 · View on GitHub

Todo一覧と消化状況を表示します。手順は以下の通りです。

  • localStorageからTodoを取得する
  • Todo表示部品(TodoContentComponent)を作成する
  • AppComponentにTodoContentComponentを追加する
  • Todo消化状況を取得する
  • Todo消化状況表示部品(TodoFooterComponent)を作成する
  • AppComponentにTodoFooterComponentを追加する

localStorageからTodoを取得する

まず、localStorageからTodoを取得する機能をServiceクラスに追加します。

:pencil2: app/services/todo.service.ts

import {Injectable} from "@angular/core";
import {Todo} from "../models/todo.model";

const STORAGE_KEY = 'angular2-todo';

@Injectable()
export class TodoService {

  todos:Todo[] = [];

  constructor() {
+    const persistedTodos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
+    // localStorageの内容をTodoModelクラスに詰め替える
+    this.todos = persistedTodos.map(todo => {
+      return new Todo(
+        todo.id,
+        todo.title,
+        todo.isCompleted
+      );
+    });
  }

  // ...省略
}

Todo表示部品(TodoContentComponent)を作成する

Todoの一覧を表示するエリアを作成します。Todoが1件も登録されていない場合は、登録されてない旨を表示します。

:pencil2: app/components/content/content.component.ts

import {Component, Input} from '@angular/core';
import {Todo} from "../../models/todo.model";

@Component({
  selector: 'todo-content',
  templateUrl: 'app/components/content/content.html'
})
export class TodoContentComponent {

  @Input()
  todos:Todo[];

  constructor() {}

}
  • @Input()は、Component外部から値を注入するためのデコレータです。これが指定されているプロパティは、Component外部から値を受け取ることができます。

:pencil2: app/components/content/content.component.ts

<ul class="list-group">
  <li class="list-group-item" *ngFor="let todo of todos; let i = index">
    <div class="row">
      <div class="col-xs-10">
        {{i + 1}}. {{todo.title}}
      </div>
      <div class="col-xs-2">
        <!--ここにあとで削除ボタンを置く-->
      </div>
    </div>
  </li>
  <li class="list-group-item text-danger" *ngIf="!todos.length">Todoがありません。</li>
</ul>
  • *ngForは、Angular1のng-repeatと等価です。
  • *ngIfは、Angular1のng-ifと等価です。

AppComponentにTodoContentComponentを追加する

TodoContentComponentを画面上に表示するためには、AppComponentに登録する必要があります。また、Todoの一覧は後ほど登場するComponentで再利用するため、AppComponent内に保持しておくようにします。

:sparkles: Todo一覧の共有方法はSharedServiceを利用するなど、他にもアプローチの仕方が存在します。

:pencil2: app/app.component.ts

import {Component} from '@angular/core';
import {TodoHeaderComponent} from './header/header.component';
+ import {TodoContentComponent} from "./content/content.component";
import {TodoService} from '../services/todo.service';
import {Todo} from "../models/todo.model";

@Component({
  selector: 'my-app',
  templateUrl: 'app/components/app.html',
  providers: [TodoService],
-  directives: [TodoHeaderComponent]
+  directives: [TodoHeaderComponent, TodoContentComponent]
})
export class AppComponent {

+  todos:Todo[];

-  constructor() {
+  constructor(private service:TodoService) {
+    this.todos = this.service.todos;
  }
}

:pencil2: app/app.html

<section class="container">
  <todo-header></todo-header>
+  <todo-content [todos]="todos"></todo-content>
</section>

<footer class="container">
  Created by YOUR_NAME
</footer>
  • [todos]="todos"は、TodoContentComponentのtodos対してAppComponentのtodosを渡します。

Todo消化状況を取得する

次にTodoの消化状況を算出する関数をServiceクラスに作成します。

:pencil2: app/services/todo.service.ts

import {Injectable} from "@angular/core";
import {Todo} from "../models/todo.model";

const STORAGE_KEY = 'angular2-todo';

@Injectable()
export class TodoService {

  todos:Todo[] = [];

  // ... 省略

+  getComplatedCount():number {
+    return this.todos.filter(todo => todo.isCompleted).length;
+  }

  private save():void {
    console.log('saving : ', this.todos);
    localStorage.setItem(STORAGE_KEY, JSON.stringify(this.todos));
  }
}

Todo消化状況表示部品(TodoFooterComponent)を作成する

Todoの消化状況はFooterに表示することにします。フッターには消化状況とTodoの合計を表示します。

:pencil2: app/components/footer/footer.html

<footer class="container">
  <p *ngIf="todos">Todo消化状況: {{getCompletedCount()}} / {{todos.length}}</p>
</footer>

:pencil2: app/components/footer/footer.component.ts

import {Component, Input} from '@angular/core';
import {TodoService} from '../../services/todo.service';
import {Todo} from "../../models/todo.model";

@Component({
  selector: 'todo-footer',
  templateUrl: 'app/components/footer/footer.html'
})
export class TodoFooterComponent {

  @Input()
  todos:Todo[];

  constructor(private service:TodoService) {}

  getCompletedCount() {
    return this.service.getComplatedCount();
  }

}

AppComponentにTodoFooterComponentを追加する

TodoFooterComponentを画面上に表示するためには、これまでと同様にAppComponentに登録する必要があります。

:pencil2: app/app.component.ts

import {Component} from '@angular/core';
import {TodoHeaderComponent} from './header/header.component';
import {TodoContentComponent} from "./content/content.component";
+ import {TodoFooterComponent} from "./footer/footer.component";
import {TodoService} from '../services/todo.service';
import {Todo} from "../models/todo.model";

@Component({
  selector: 'my-app',
  templateUrl: 'app/components/app.html',
  providers: [TodoService],
-  directives: [TodoHeaderComponent, TodoContentComponent]
+  directives: [TodoHeaderComponent, TodoContentComponent, TodoFooterComponent]
})
export class AppComponent {

  todos:Todo[];

  constructor(private service:TodoService) {
    this.todos = this.service.todos;
  }
}

:pencil2: app/app.html

<section class="container">
  <todo-header></todo-header>
  <todo-content [todos]="todos"></todo-content>
+  <todo-footer [todos]="todos"></todo-footer>
</section>

<footer class="container">
  Created by YOUR_NAME
</footer>

以上でこの章は終了です。

画面上にTodo一覧と消化状況が表示されていれば、次に進んでください。