もくじ
- #01 Hello Worldしてみよう!
- #02 雛形アプリのソースコードを覗いてみよう!
- #03 Todoアプリ開発開始!新しくコンポーネントを作ってみよう!
- #04 コンポーネントを連携させてみよう!
- #05 Coming Soon…
はじめに
カルテットコミュニケーションズ開発部ブログで Angular超入門 の連載をスタートしてから、かなりの間が空いてしまいました。🙇前回の投稿からすでに半年が経過してしまいましたが、ぼちぼち再開しようと思います。また、金本からのバトンタッチを受け、今回から松岡が執筆を担当します。
さて、前回の投稿 Todoアプリ開発開始!新しくコンポーネントを作ってみよう! では todo
を入力するためのシンプルなコンポーネントを作成しました。
ブラウザにはこのような入力画面が表示されます。
ちょっと味気ないですね…。今回はこのコンポーネントをベースに todo
をリスト管理できるようにしてみたいと思います!
まだチュートリアルを実践していない方は Hello Worldしてみよう! から試してみてくださいませ。10分足らずで終わるはずです。
今回のゴール
todo
リストを表示し、追加ボタンでリストの要素を増やせるようにします。
前回作った TodoFormComponent に加え、今回は TodoListComponent を新たに追加します。この2つのコンポーネントの親である AppComponent を通して todo
データをやり取りしてみましょう。
ちなみに本文中の「親」「子」とは、とあるコンポーネント(親)に別のコンポーネントを設置している(子)、という関係を指しています。
DOM のイベントを拾ってみよう!
ボタンを設置する
前回までのチュートリアルのソースコードをベースに TodoFormComponent に追加ボタンを設置してみましょう。
src/app/components/todo-form/todo-form.component.html
<input type="text" [(ngModel)]="todo.name">
<input type="checkbox" [(ngModel)]="todo.isDone">
+ <button (click)="clicked()">追加</button>
src/app/components/todo-form/todo-form.component.ts
@Component({
selector: 'app-todo-form',
...
})
export class TodoFormComponent implements OnInit {
...
+ clicked() {
+ console.log(this.todo);
+ }
イベントバインディング
(click)
のような書き方を イベントバインディング と呼びます。(change)
(focus)
(blur)
など()
には DOM の標準イベント の名前が入ります。
(click)="clicked()"
のように書くと イベント発火時にコンポーネントのメソッドを呼び出す 事ができます。
これで、追加ボタンを押した時に任意の処理が実行できるように準備が整いました。
ng serve --open
してブラウザで追加ボタンをクリックしてみてください。入力した todo
の内容がデバッグコンソールに出力されるはずです。
親子関係のコンポーネントでデータを連携させてみよう!
子コンポーネントでイベント発火する
TodoFormComponent は親である AppComponent に「追加ボタンが押された」事を知らせる必要があります。
子コンポーネントから親コンポーネントに 何らかのタイミング を伝えるには @Output
と EventEmitter
を使います。
src/app/components/todo-form/todo-form.component.ts
- import { Component, OnInit } from '@angular/core';
+ import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { Todo } from 'src/app/models/todo';
@Component({
selector: 'app-todo-form',
...
})
export class TodoFormComponent implements OnInit {
+ @Output() addTodo = new EventEmitter<Todo>();
public todo: Todo = {
name: '',
isDone: false,
};
constructor() { }
ngOnInit(): void {
}
clicked() {
- console.log(this.todo);
+ this.addTodo.emit(this.todo);
}
}
@Output() / EventEmitter
カスタムイベントを発火させる仕組みです。
@Output() addTodo
はこのコンポーネントが addTodo というカスタムイベントを持っている事を宣言 しています。emit()
を呼び出すと カスタムイベントが発火 します。
emit()
には、カスタムイベントのパラメータを渡す事ができます。
- 数値
this.addTodo.emit(1)
- 文字列
this.addTodo.emit("abc")
- オブジェクト
this.addTodo.emit({ id: 1 })
上記のソースコードでは this.addTodo.emit(this.todo)
としているため、コンポーネントの持つ todo
をパラメータとして渡している事になります。
これで、TodoFormComponent が入力内容の todo
を親コンポーネントに伝えられるようになりました。
親コンポーネントがイベントを拾う
次に親コンポーネントである AppComponent で「追加ボタンが押された」イベントを拾ってみましょう。
DOMの標準イベントをキャッチするのに (click)="method()"
と書くように、カスタムイベントも (イベント名)="method()"
という書き方をします。
src/app/app.component.html
- <app-todo-form></app-todo-form>
+ <app-todo-form (addTodo)="added($event)"></app-todo-form>
パラメータを受け取る時はイベントバインディングの引数に
$event
を指定します。
src/app/app.component.ts
import { Component } from '@angular/core';
+ import { Todo } from './models/todo';
@Component({
selector: 'app-root',
...
})
export class AppComponent {
title = 'angular-todo';
+ public appTodos: Todo[] = [];
+ added(todo: Todo) {
+ this.appTodos.push({
+ name: todo.name,
+ isDone: todo.isDone,
+ });
+ }
}
added() の引数名は、
$event
に縛られません。任意に変更できます。
これで、AppComponent が TodoFormComponent のイベントを拾って、受け取った todo
をリストに追加するようになりました。
リスト表示用コンポーネントを作成
次に AppComponent が持っている todo
のリストを画面に表示するために、専用のコンポーネントを作成します。
$ ng g c components/todo-list
ng generate component
コマンドは頭文字を取ってこのように短くタイプする事もできます。
コマンドによって <todo-list>
が追加されているはずです。
> $ tree src/app/components/
src/app/components/
├── todo-form
│ ├── ...
└── todo-list
├── todo-list.component.html
├── todo-list.component.scss
├── todo-list.component.spec.ts
└── todo-list.component.ts
子コンポーネントが値を受け取る準備をする
TodoListComponent は親コンポーネントである AppComponent から todo
のリストを受け取る必要があります。
子コンポーネントが 親コンポーネントから値を受け取る には @Input
を使います。
src/app/components/todo-list/todo-list.component.ts
- import { Component, OnInit } from '@angular/core';
+ import { Component, Input, OnInit } from '@angular/core';
import { Todo } from 'src/app/models/todo';
@Component({
selector: 'app-todo-list',
templateUrl: './todo-list.component.html',
styleUrls: ['./todo-list.component.scss']
})
export class TodoListComponent implements OnInit {
+ @Input() todos: Todo[] = [];
constructor() { }
ngOnInit(): void {
}
}
@Input()
データバインディングでデータを受け取る仕組みです。
@Input() todos
のように宣言すると親コンポーネントから[todos]="値"
のように値を受け渡しする事ができます。
これで TodoListComponent が todo
のリストを受け取る準備ができました。何が渡されたのか分かるようにオブジェクトの中身をデバッグ出力しておきましょう。
src/app/components/todo-list/todo-list.component.html
- <p>todo-list works!</p>
+ <pre>{{todos|json}}</pre>
親コンポーネントから子コンポーネントに値を渡す
さて、ようやく連携の準備が整いました。AppComponent の HTML に リスト表示コンポーネントを設置 してみましょう。
src/app/app.component.html
<app-todo-form (addTodo)="added($event)"></app-todo-form>
+ <app-todo-list [todos]="appTodos"></app-todo-list>
AppComponent の動作:
-
(addTodo)
イベントバインディングを通して TodoFormComponent からtodo
を受け取ります - 受け取った
todo
を変数appTodos
に追加します -
[todos]
データバインディングを通して TodoListComponent にappTodos
を渡します
動作確認
ブラウザで確認してみてください。
TodoFormComponent
で入力した内容が、親である AppComponent
を通して TodoListComponent
に伝わるはずです。
まとめ
DOM の標準イベントを拾う。
<button (name)="method()">
子コンポーネントのイベントを拾う。
class ChildComponent {
@Output() myEvent = new EventEmitter<ParamType>();
method() {
const param: ParamType = {...};
this.myEvent.emit(param);
}
}
// 親
<child-component (myEvent)="method($event)">
子コンポーネントに値を渡す。
class ChildComponent {
@Input() name: Type;
}
// 親
<child-component [name]="value">
次回予告
次回は、繰り返しを制御する *ngFor
や分岐を制御する *ngIf
を使って、ビューをカスタマイズしてみたいと思います。
お楽しみに!