コンテンツへスキップ
@Output と EventEmitter を使用したコンポーネント データ フローのAngular

@Output と EventEmitter を使用したコンポーネント データ フローのAngular

AngularJS とは異なり、Angularには双方向のデータ バインディングがありません。私が言うとき、Angularには双方向のデータバインディングがありません、それはあなたがそれを達成できないという意味ではありません。

15min read

最新の Web フレームワークは、双方向のデータ バインディングのサポートなしには存在できません。Angularは ngModel ディレクティブを使用してこの関数を提供します。これを使用するには、FormsModuleをインポートする必要があり、次のようなさまざまな方法で使用できます。

  • ngModel  
  • [ngModel] 
  • [(ngModel)] 

なぜAngularには AngularJS のような双方向データ バインディングがないのか疑問に思うかもしれません。AngularJS はかつてダイジェスト サイクル、ダーティ チェック アルゴリズムなどを使用して双方向データ バインディングを実装していましたが、これらのアプローチには独自の問題がありました。この記事では、AngularJS双方向データバインディングの問題に焦点を当てて、Angularで異なる方法で処理される理由を理解します。

Angularに戻ると、双方向のデータバインディングは次のように表すことができます。

 

双方向データバインディングは、次のように表現できます

 

角かっこはプロパティ バインディングを表し、小角かっこはイベント バインディングを表し、両方の組み合わせは双方向のデータ バインディングを表します。双方向データバインディングの構文は、ボックス構文ではbananaとも呼ばれます。データバインディングの詳細については、こちらをご覧ください

これで、Angularのさまざまな種類のバインディングについてある程度理解できました。Angularコンポーネント間でデータがどのように流れるかを見てみましょう。データは、プリミティブ型、配列、オブジェクト、またはイベントにすることができます。コンポーネント間でデータをフローするために、Angularは以下を提供します。

  1. @Input decorator  
  2. @Output decorator  

これらのデコレータは両方とも@angular/coreの一部です。

@Input() decorator marks a class field as an input property and supplies configuration metadata. It declares a data-bound input property, which Angular automatically updates during change detection. 

デコレーター@Outputクラスフィールドを出力プロパティとしてマークし、構成メタデータを提供します。データバインドされた出力プロパティを宣言し、変更検出中に自動的に更新Angular。

これらのデコレータは両方とも@angular/coreの一部です

 

これら 2 つのデコレーターは、コンポーネント間でデータを流すために使用されます。これら 2 つのデコレーターの他に、Angularサービスを使用してコンポーネント間でデータを流すこともできます。コンポーネントにデータを渡す必要がある場合はデコレータを使用し@Inputコンポーネントからデータを出力する必要がある場合は@Outputデコレータを使用します。

どちらのデコレーターも、コンポーネントが相互に関連している場合、コンポーネント間で機能します。 たとえば、AppComponent という別のコンポーネント内で ChildComponent というコンポーネントを使用している場合、それらは相互に関連付けられています。 これは下の画像で確認でき、デコレータでデコレートされた ChildComponent プロパティ@Input AppComponent からデータを受け取ることができます。

どちらのデコレータも、コンポーネントが相互に関連している場合、コンポーネント間で機能します

理解を深めるために、以下に示す ChildComponent を検討してください。

import {
    Component,
    OnInit,
    Input
}

from '@angular/core';

@Component({
    selector: 'app-child',
    template: ` <p>count= {
            {
            count
        }
    }
    </p> `
}) export class ChildComponent implements OnInit {
    constructor() {}
    @Input() count: number;
    ngOnInit() {}
}

上記のコードスニペットをよく見ると、countプロパティで@Input()デコレータを使用しており、countプロパティの値がChildComponentの外部から設定されることを意味します。 AppComponent内でChildComponentを使用し、以下のリストに示すように、プロパティバインディングを使用してcountプロパティの値を渡すことができます。

import { Component } from "@angular/core";

@Component({
  selector: "app-root",
  template: `<h2>{{ title }}</h2>

    <app-child [count]="count"></app-child> `,
})
export class AppComponent {
  count = 9;
}

AppComponent内でChildComponentを使用し、ChildComponentでデータを渡すプロパティバインディングを使用しています。 現在、データは一方向の方向の流れで ChildComponent に渡されています。 入力バインドされたプロパティの値が更新されるたびに、Angularは変更検出器を実行することに注意してください。ただし、デコレーターで変更検出器を実行する方法@Input構成できます。また、デコレータプロパティ@input更新されるたびに、Angularは ngOnChanges() ライフサイクルフックを実行するため、ngOnChanges() ライフサイクルフックで入力バインドプロパティの更新された値を読み取ることができます。

親コンポーネントから子コンポーネントにデータを渡す方法がわかったので、子コンポーネントから親コンポーネントにデータとイベントを出力する方法に焦点を切り替えましょう。 コンポーネントからデータとイベントを出力するには、次のものが必要です

  • デコレーターでプロパティを装飾@output。これにより、このプロパティはアウトバウンド・データ・プロパティーになります。
  • EventEmitter のインスタンスを作成し、デコレータでデコレート@Outputします。

下の画像では、データまたはイベントがデコレーターを使用してコンポーネントの外部@Output移動することに注意してください。

下の画像では、データまたはイベントがデコレータを使用してコンポーネントの外部@Output移動することに注意してください

 

EventEmitter の動作を詳細に説明する前に、以下のコード例を確認してください。 以下のリストに示すように、ボタンを使用して ChildComponent を変更します。

import {
    Component,
    OnInit,
    Input
}

from '@angular/core';

@Component({
    selector: 'app-child',
    template: ` <p>count= {
            {
            count
        }
    }

    </p> <button (click)='updateCount()' >update count </button> `

}) 
export class ChildComponent implements OnInit {

    constructor() {}
    @Input() count: number;
    ngOnInit() {}
    updateCount() {
        this.count=this.count+1;
    }

}

ChildComponentのボタンをクリックすると、カウント値が更新されます。超シンプル。 これまでのところ、クリックイベントはChildComponent自体内で処理されます。

それでは、要件を少し調整してみましょう。ChildComponent内のボタンのクリックイベントに対してAppComponentの関数を実行したい場合はどうすればよいでしょうか ?

これを行うには、ChildComponent からボタンクリックイベントを発行する必要があります。イベント出力を使用すると、ChildComponentからデータを送信することもできます。 AppComponentでキャプチャするデータとイベントを出力するようにChildComponentを変更しましょう。

import {
    Component,
    OnInit,
    Input,
    Output,
    EventEmitter
}

from '@angular/core';

@Component({

    selector: 'app-child',

    template: ` <p>count= {
            {
            count
        }
    }
    </p><button (click)='updateCount()' >update count </button> `

}) 
export class ChildComponent implements OnInit {

    constructor() {}

    @Input() count: number;

    @Output() countChange=new EventEmitter();

    ngOnInit() {}

    updateCount() {

        this.count=this.count+1;

        this.countChange.emit(this.count);

    }

}

現在、ChildComponent で次のタスクを実行しています

  1. countChange という EventEmitter のインスタンスが作成され、ボタンのクリック イベントで親コンポーネントに出力されます。
  2. updateCount() という名前の関数を作成しました。この関数はボタンのクリックイベントで呼び出され、関数イベント内でcountChangeが発行されます。
  3. countChange イベントを発行している間、count プロパティの値もコンポーネントから送信されます。

先に進む前に、EventEmitterについて理解しましょう。これは、ディレクティブとコンポーネントで、カスタムイベントを同期的または非同期的に発行するために使用されます。 どのハンドラも、EventEmitter クラスのインスタンスをサブスクライブすることで、これらのイベントを処理できます。 これは、ハンドラがイベントをサブスクライブできるようにRxJSオブザーバブルを使用するため、通常のHTMLイベントとは異なります。

EventEmitterクラスの実装を調べると、Subjectクラスが拡張されます。

EventEmitterクラスの実装を調べると、Subjectクラスを拡張します

  

EventEmitter クラスは RxJs Subject クラスを拡張するため、これは監視可能であり、多くのオブザーバにマルチキャストできることを意味します。 DOMイベントと同じではありません。ミューティキャストして監視することはできません。

AppComponent では、次のコード リストに示すように、ChildComponent から出力されたイベントを処理できます。

import { Component } from '@angular/core'; 

@Component({ 
  selector: 'app-root', 
  template: `<h2>{{title}}</h2> 
  <app-child [count]='count' (countChange)=changeCount($event)></app-child>` 
}) 

export class AppComponent { 
   count = 9; 
   changeCount(data) { 
      console.log(data); 
   } 
}

現在、AppComponentクラスで次のタスクを実行しています。

  1. テンプレートで <app-child> を使用します。
  2. <app-child>要素で、イベントバインディングを使用してcountChangeイベントを使用します。
  3. countChange イベントで changeCount 関数を呼び出します。
  4. changeCount関数で、ChildComponentから渡されたカウントの値を出力します。

ご覧のとおり、AppComponent の関数は、ChildComponent に配置されたボタンのクリック イベントで呼び出されます。これは、@OutputとEventEmitterで行うことができます。 現在、データは@Input()と@Output()デコレータを使用してコンポーネント間で流れています。

 

現在、データは@Input()と@Output()デコレータを使用してコンポーネント間で流れています。

ご覧のとおり、2 つのコンポーネント間に双方向のデータ バインディングを作成しました。コードを見ると、プロパティバインディングとイベントバインディングを組み合わせたものです。

コードを見ると、プロパティバインディングとイベントバインディングの組み合わせです

リアルタイムの例

リアルタイムの例を取り上げて、@Output と EventEmitter がどのように役立つかを見てみましょう。AppComponentが、次の図に示すように、製品の一覧を表形式でレンダリングしているとします。

リアルタイムの例を取り上げて、@OutputとEventEmitterがどのように役立つかを調べます

上記の製品テーブルを作成するために、製品のリストを返すという 1 つの関数のみを持つ非常に単純なAppComponentクラスがあります。

export class AppComponent implements OnInit { 
  products = []; 
  title = 'Products'; 
  ngOnInit() { 
    this.products = this.getProducts(); 
  } 

  getProducts() { 
    return [ 
      { 'id': '1', 'title': 'Screw Driver', 'price': 400, 'stock': 11 }, 
      { 'id': '2', 'title': 'Nut Volt', 'price': 200, 'stock': 5 }, 
      { 'id': '3', 'title': 'Resistor', 'price': 78, 'stock': 45 }, 
      { 'id': '4', 'title': 'Tractor', 'price': 20000, 'stock': 1 }, 
      { 'id': '5', 'title': 'Roller', 'price': 62, 'stock': 15 }, 
    ]; 
  } 
}

ngOnInit ライフサイクルフックでは、getPrdoducts() 関数を呼び出し、返されたデータを products 変数に割り当てて、テンプレートで使用できるようにします。そこでは、 *ngForディレクティブを使用して配列を反復処理し、製品を表示しています。以下のコードを参照してください。

<div class="container"> 
  <br/> 
  <h1 class="text-center">{{title}}</h1> 
  <table class="table"> 
    <thead> 
      <th>Id</th> 
      <th>Title</th> 
      <th>Price</th> 
      <th>Stock</th> 
    </thead> 
    <tbody> 
      <tr *ngFor="let p of products"> 
        <td>{{p.id}}</td> 
        <td>{{p.title}}</td> 
        <td>{{p.price}}</td> 
        <td>{{p.stock}}</td> 
      </tr> 
    </tbody> 
  </table> 
</div>

このコードでは、次の図に示すように、製品がテーブルにレンダリングされます。

このコードでは、製品は画像に示すようにテーブルにレンダリングされます

次に、下の画像に示すように、ボタンと入力ボックスを含む新しい列を追加するとします。

図のように、ボタンと入力ボックスを含む新しい列を追加するとします

 

当社の要件は次のとおりです。

  1. ストックの値が10を超える場合、ボタンの色は緑色になります。
  2. 在庫の値が10未満の場合、ボタンの色は赤になります。
  3. ユーザーは入力ボックスに数値を入力することができ、その特定の株式価値に追加されます。
  4. ボタンの色は、製品在庫の変更値に基づいて更新する必要があります。

このタスクを実現するために、StockStausComponentという新しい子コンポーネントを作成しましょう。基本的に、StockStatusComponentのテンプレートには、1つのボタンと1つの数値入力ボックスがあります。StockStatusComponentの場合:

  1. AppComponnetから渡された株式の値を読み取る必要があります。このために@Inputは、
  2. StockStatusComponentボタンのクリックでAppComponentの関数を呼び出せるように、イベントを発行する必要があります。このためには、@OutputとEventEmitterを使用する必要があります。

 

Consider the code below: 

stockstatus.component.ts

import { Component, Input, EventEmitter, Output, OnChanges } from '@angular/core'; 

@Component({ 
    selector: 'app-stock-status', 
    template: `<input type='number' [(ngModel)]='updatedstockvalue'/> <button class='btn btn-primary' 
     [style.background]='color' 
     (click)="stockValueChanged()">Change Stock Value</button> ` 
}) 

export class StockStatusComponent implements OnChanges { 
    @Input() stock: number; 
    @Input() productId: number; 
    @Output() stockValueChange = new EventEmitter(); 
    color = ''; 
    updatedstockvalue: number; 

    stockValueChanged() { 
        this.stockValueChange.emit({ id: this.productId, updatdstockvalue: this.updatedstockvalue }); 
        this.updatedstockvalue = null; 
    } 

    ngOnChanges() { 
        if (this.stock > 10) { 
            this.color = 'green'; 
        } else { 
            this.color = 'red'; 
        } 
    } 
}

上記のクラスを行ごとに調べてみましょう。

  1. 最初の行では、必要なものをすべてインポートしています:@Input、@Outputなど。
  2. テンプレートには、更新されたストック値プロパティを使用して[(ngモデル)].この値をイベントとともにアプリコンポーネント.
  3. テンプレートには、ボタンが 1 つあります。ボタンのクリックイベントで、イベントがAppComponentに出力されます。
  4. 製品の在庫の価値に基づいてボタンの色を設定する必要があります。したがって、プロパティ バインディングを使用してボタンの背景を設定する必要があります。color プロパティの値がクラスで更新されます。
  5. 2 つの @Input() 装飾されたプロパティ (stockproductId) を作成しています。この 2 つのプロパティの値はAppComponentから渡されるためです。
  6. stockValueChangeというイベントを作成しています。このイベントは、ボタンをクリックするとAppComponentに発行されます。
  7. stockValueChanged関数では、stockValueChangeイベントを発行し、更新する製品 ID と製品在庫値に追加する値も渡します。
  8. AppComponentでストック値が更新されるたびに、color プロパティの値を更新する必要があるため、ngOnChanges() ライフサイクルフックの color プロパティの値を更新しています。

ここでは、@Inputデコレータを使用して、この場合はたまたま親クラスであるAppComponentクラスからデータを読み取ります。親コンポーネントクラスから子コンポーネントクラスにデータを渡すには、デコレータ@Input使用します。

さらに、EventEmitterで @Output を使用して、AppComponentにイベントを発行しています。したがって、子コンポーネントクラスから親コンポーネントクラスにイベントを出力するには、@Output()デコレータでEventEmitterを使用します。

したがって、StockStatusComponentは @Input と @Output の両方を使用してAppComponentからデータを読み取り、AppComponentにイベントを発行します。

Modify AppComponent to use StockStatusComponent 

まず、テンプレートを変更しましょう。テンプレートに、新しいテーブル列を追加します。列内では、<app-stock-status> コンポーネントが使用されます。

<div class="container"> 
  <br/> 
  <h1 class="text-center">{{title}}</h1> 
  <table class="table"> 
    <thead> 
      <th>Id</th> 
      <th>Title</th> 
      <th>Price</th> 
      <th>Stock</th> 
    </thead> 
    <tbody> 
      <tr *ngFor="let p of products"> 
        <td>{{p.id}}</td> 
        <td>{{p.title}}</td> 
        <td>{{p.price}}</td> 
        <td>{{p.stock}}</td> 
        <td><app-stock-status [productId]='p.id' [stock]='p.stock' (stockValueChange)='changeStockValue($event)'></app-stock-status></td> 
      </tr> 
    </tbody> 
  </table> 
</div>

プロパティバインディング(これら2つのプロパティはStockStatusComponentで@Input()で修飾されていることを覚えておいてください)と、イベントバインディングを使用してstockValueChangeイベントを処理する(このイベントはStockStatusComponentで@Output()で修飾されていることに注意してください)を使用して、productIdstockに値を渡しています。

次に、AppComponentchangeStockValue関数を追加する必要があります。AppComponentクラスに次のコードを追加します。

productToUpdate: any; 

changeStockValue(p) { 
    this.productToUpdate = this.products.find(this.findProducts, [p.id]); 
    this.productToUpdate.stock = this.productToUpdate.stock + p.updatdstockvalue; 
  } 

  findProducts(p) { 
    return p.id === this[0]; 
  }

この関数では、JavaScript Array.prototype.findメソッドを使用して、一致する productIdを持つ製品を検索し、一致した製品の在庫数を更新しています。 アプリケーションを実行すると、次の出力が得られます。

数値ボックスに数値を入力してボタンをクリックすると、親コンポーネントの操作値を更新するタスクが子コンポーネントで実行されます。また、親コンポーネントの値に基づいて、子コンポーネントでスタイルが変更されます。これはすべて、Angular @Input、@Output、EventEmitter を使用して可能です。

Angularの他の機能についてさらに詳しく説明する今後の記事にご期待ください!

Ignite UI for Angular利点

 

デモを予約