@Outputデコレーターを使って、子コンポーネントで行われる処理や情報をイベント経由で親コンポーネントへ引き渡す方法を解説。
Angular 5以降。v5時点で執筆し、v6で動作を確認しました。
別稿「Angularで親コンポーネントから子コンポーネントに値を引き渡すには?」では、@Inputデコレーターを利用して、属性経由で親⇒子コンポーネントへ情報を引き渡す方法について、解説しました。
@Outputデコレーターは、この@Inputと対をなす仕組みです。子コンポーネントで発生したイベントを介して、親コンポーネントに情報を引き渡します。@Inputデコレーターに比べると直観的に理解しづらいと思うので、本稿で作成するサンプルを例に見てみましょう。以下は、前掲の別稿で作成したサンプルに、記事登録フォームを追加したものです。
ページ全体をAppComponentコンポーネントが担当し、その配下でEditArticleComponentコンポーネントが入力フォームを担うという役割分担です。EditArticleComponentは、[登録]ボタンをクリックしたタイミングでcreatedイベントを発生し、AppComponentに新規の記事情報(Articleオブジェクト)を通知します。AppComponentでは、受け取ったArticleオブジェクトを基に記事情報を追加します。
以上の関係を念頭に、ここからは具体的なコードを見ていきます。なお、サンプルコードは別稿からの差分だけを示します。サンプルの全体像は、別稿とダウンロードサンプルを参照してください。
子コンポーネントであるEditArticleComponentコンポーネントのコードは、以下の通りです。
import { Component, EventEmitter, Output } from '@angular/core';
import { Article } from './article';
@Component({
selector: 'my-edit-article',
templateUrl: './edit-article.component.html',
styleUrls: ['./edit-article.component.css']
})
export class EditArticleComponent {
createdItem: Article = new Article();
// [1]createdイベントの宣言
@Output() created = new EventEmitter<Article>();
onsubmit() {
// [2]createdイベントを発生($eventにはArticleオブジェクトを代入)
this.created.emit(this.createdItem);
// 入力フォームをクリア
this.createdItem = new Article();
}
}
<form #myForm="ngForm" (ngSubmit)="onsubmit()">
<!--[3]入力値をcreatedItem.xxxxxに割り当て-->
<div>
<label for="url">URL:</label><br />
<input type="text" id="url" name="url" size="25" [(ngModel)]="createdItem.url" />
</div>
<div>
<label for="title">タイトル:</label><br />
<input type="text" id="title" name="title" size="25" [(ngModel)]="createdItem.title" />
</div>
<div>
<label for="author">著者名:</label><br />
<input type="text" id="author" name="author" size="15" [(ngModel)]="createdItem.author" />
</div>
<div>
<label for="view">ビュー数:</label><br />
<input type="number" id="view" name="view" size="6" [(ngModel)]="createdItem.view" />
</div>
<div>
<label for="updated">更新日:</label><br />
<input type="date" id="updated" name="updated" [(ngModel)]="createdItem.updated" />
</div>
<div>
<input type="submit" value="登録" />
</div>
</form>
ここでポイントとなるのは[1]のコード――createdイベントの宣言です。イベント宣言の一般的な構文は、以下の通りです。
@Output() event = new EventEmitter<type>()
「<type>」の部分は、イベントが発生したときにイベントオブジェクト($event)として渡すデータの型を表します。よって、[1]の「new EventEmitter<Article>()」であれば「イベント発生時にArticleオブジェクトを渡すcreatedイベント」を宣言していることになります。
もっとも、@Outputデコレーターは、イベント名と型を宣言しているだけなので、イベントを発生する箇所は別に示さなければなりません。これが[2]のemitメソッドです。
this.event.emit(obj)
サンプルでは、onsubmitメソッド――[登録]ボタンがクリックされたタイミングでemitメソッドを呼び出し、フォームから入力された記事情報(this.createdItem)を引き渡しています(createdItemの各プロパティには、[3]以降の部分でngModelディレクティブを経由して個々の入力値を割り当てている点にも注目です)。
emitメソッドの引数objは、先ほどEventEmitter<type>で宣言した型と一致していなければなりません(この場合はArticle型)。
EditArticleComponentコンポーネントを呼び出し、そのcreatedイベントを受け取るのは親コンポーネントであるAppComponentの役割です。
import { Component } from '@angular/core';
import { Article } from './article';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
...中略...
// [2]createdイベント発生時に記事情報を追加
oncreated(article: Article) {
this.articles.push(article);
}
}
<ul>
<li *ngFor="let article of articles">
<a href="#" (click)="onclick(article)">{{article.title}}</a>
</li>
</ul>
...中略...
<!--[1]createdイベントハンドラーを設定-->
<my-edit-article (created)="oncreated($event)"></my-edit-article>
[1]は、Event Bindingの構文です。EditArticleComponentでcreatedイベントが発生したタイミングで、oncreatedメソッドを呼び出します。先ほども触れたように、$eventオブジェクトには、emitメソッド経由で渡された記事情報(Articleオブジェクト)が格納されています。
後は、oncreatedメソッド([2])で渡された記事情報(引数article)を、pushメソッドで元の記事情報(this.articles)に追加するだけです。
コンポーネントを追加した場合には、アプリ側できちんと認識できるように、モジュール側にも追加しておきましょう*1。また、ここではAngularのフォーム機能(ngModelディレクティブ)を使っているので、そのためのFormsModuleモジュールも合わせてインポートしています。
*1 Angular CLIのng generateコマンドを利用した場合には、自動的に登録されます。
@NgModule({
declarations: [
AppComponent,
DetailsArticleComponent,
EditArticleComponent,
],
imports: [
BrowserModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
以上を理解できたら、サンプルを実行してみましょう。冒頭の図のように、記事情報を入力して[登録]ボタンをクリックすると、記事一覧に新規記事が追加されます。
Copyright© Digital Advantage Corp. All Rights Reserved.