はじめてAngular2を触ってみた初心者のメモ書きです。
前回の投稿から、公式チュートリアルをチラ見しながらTODOアプリを作っています。
今回は、前回作ったappコンポーネントをサクサクと分割していきたいと思います。
チュートリアルで言うとこのあたりです。
今回もAngular CLIの恩恵を享受していきます。
目次
コンポーネントを分割する
コンポーネントを作成(ng generate component)
ファイル名はケバブケースでコンポーネント作成します。(最初キャメルにしたらAngular CLIに直された)
ng g component task-detail
作成したコンポーネントをインポート
前回同様Taskクラスをインポートします。import { Task } from './task';
app.component.ts、task-detail.component.tsどちらにもインポート文が必要です。
親コンポーネントのプロパティをバインド
また子コンポーネントのプロパティに@Input()
アノテーションを付けることで、
親コンポーネントの値がバインドされるようになります。
よくわかんないけど親側での変更が反映されるんだな~と思っておきます。
task-detail.component.ts
//ここでInputアノテーションが使えるようにInputをインポートしてます import { Component, OnInit, Input } from '@angular/core'; import { Task } from './task'; @Component({ selector: 'app-task-detail', templateUrl: './task-detail.component.html', styleUrls: ['./task-detail.component.scss'] }) export class TaskDetailComponent implements OnInit { @Input() task: Task; constructor() { } ngOnInit() { } }
ディレクティブを置き換えてコンポーネント表示
task-detail.component.html
<div *ngIf="task"> <h2>{{task.name}} details!</h2> <div><label>id: </label>{{task.id}}</div> <div> <label>name: </label> <input [(ngModel)]="task.name" placeholder="name"/> </div> </div>
今まで↑のディレクティブが書いてあった部分を今作ったコンポーネントに置き換えます。
selectedTaskをtaskというプロパティ名で子コンポーネントに送ってバインドしています。
app.component.html
<app-task-detail [task]="selectedTask"></app-task-detail>
ヌゥ…なんでエラー
ERROR in C:/angularWork/my-new-app/src/app/task-detail/task-detail.component.ts (3,22): Cannot find module './task'.) ERROR in ./src/app/task-detail/task-detail.component.ts Module not found: Error: Can't resolve './task' in 'C:\angularWork\my-new-app\src\app\task-detail' @ ./src/app/task-detail/task-detail.component.ts 11:0-30 @ ./src/app/app.module.ts @ ./src/main.ts @ multi webpack-dev-server/client?http://localhost:4200/ ./src/main.ts
appコンポーネントにも同じインポート文書いてるのにな?と思ったら
task-detail.component.tsはapp/task-detail/下にあったのでした。
import { Task } from './task';
じゃなくて
import { Task } from '../task';
でした。
変なところで時間使ってしまった。
これでコンポーネント分割できました~見た目は全く変わってませんが。
サービスを使う
気を取り直して、サービスを作っていきます。
チュートリアルで言うとこのあたりです。
サービスを作る(ng generate service)
新しいコマンドですが、いつものようにng g
ng g service task installing service create src\app\task.service.spec.ts create src\app\task.service.ts WARNING Service is generated but not provided, it must be provided to be used
not provided?ということは何かやらないと使えないのか…
app下にtask.service.tsが作られてました。
中身はこう。
import { Injectable } from '@angular/core'; @Injectable() export class TaskService { constructor() { } }
@Injectable()アノテーションは最初からつけておいてくれるんですね。
モックデータを移動する
今までapp.component.tsで宣言していた配列を別ファイルに分離させます。
Taskクラスも忘れずにインポート。
src/app/mock-tasks.ts
import { Task } from './task'; export const TASKS: Task[] = [ { id: 11, name: '企画ロードマップ作成' }, { id: 12, name: '山田さんにメール返信' }, { id: 13, name: 'Angular2キャッチアップ' }, { id: 14, name: 'ブログ更新' }, { id: 15, name: '新卒技術研修' } ];
親コンポーネントのプロパティであるところのtasksは初期化しないように変更します。
app.component.ts
export class AppComponent { private title = 'todo app'; private tasks: Task[];// ここです private selectedTask: Task; }
task.service.tsでモックデータを受け取って、返却するようにします。
import { Injectable } from '@angular/core'; import { Task } from './task'; import { TASKS } from './mock-tasks'; @Injectable() export class TaskService { public getTasks(): Task[] { return TASKS; } constructor() { } }
TaskServiceをインポートする
作ったサービスをAppComponentで使えるようにしていきます。
いつものようにインポート文を加えて
import { TaskService } from './task.service';
コンストラクタでサービスインスタンスを取得します。newして呼び出してはいけないそうです。
export class AppComponent { private title = 'todo app'; private tasks: Task[]; private selectedTask: Task; constructor(private taskService: TaskService) { } public onSelect(task: Task): void { this.selectedTask = task; } }
で保存したらエラー
そういえばさっきnot providedって言われてました。
app.module.tsでproviders
を宣言しないといけないようです。
app.module.ts
@NgModule({ ・・・ providers: [TaskService], ・・・ })
これでエラーが消えました。一安心。
サービスのgetTasks()を宣言しなおします。
app.component.ts
private getTasks(): void { this.tasks = this.taskService.getTasks(); }
ngOnInitというメソッドが、コンポーネントの生成後に呼ばれるそうなので、
ここでさっき作ったgetTasks()を呼んでもらいます。
app.component.ts
import { Component, OnInit } from '@angular/core'; // OnInitもインポート ・・・ export class AppComponent implements OnInit { // implementsでOnInitを使えるようにして private title = 'todo app'; private tasks: Task[]; private selectedTask: Task; constructor(private taskService: TaskService) { } private getTasks(): void { this.taskService.getTasks().then(tasks => this.tasks = tasks); } // ここで使う ngOnInit() { this.getTasks(); } }
これでモックデータを呼んでくることができるようになりました~
見た目は全く変わってませんが。
非同期でサービスを使う
promise使って非同期でモックデータを持ってきてみます。
TaskServiceのgetTasks()メソッドでTasksを返していたところを、
Promise<Tasks>を返すように変更します。
task.service.ts
public getTasks(): Promise<Task[]> { return Promise.resolve(TASKS); }
tasks.component.ts
private getTasks(): void { this.taskService.getTasks().then(tasks => this.tasks = tasks); }
アロー関数、初めて使いました。
thisを別名で宣言しなくて良いんですね。
これで非同期でデータを取ってくることができるようになりました~
見た目は全く変わってませんが。
ソースコードまとめ
感想
なんだか大変だったわりに見た目は全く変わっていません。
何にもやってないみたいじゃないか、いやいや学びはありました。
次回でもう少しは体裁を整えられるのか、TODOアプリを作る③ ルーティング編、乞うご期待!