Angular勉強会 #3 Component と Binding

## 前回のおさらいと今回やること
- Angularは `NgModule` と呼ばれるモジュールシステムによってモジュール化されている
- `NgModule` にAngularを構成する以下の主要要素を定義する
    - `Component` を始めとするView要素
    - ロジックのみを持つ `Service` クラス
- 今回はView要素の主要要素である `Component` について

---

## `Component` とは
コンポーネントはAngularにおいてViewを表現するもので、主要なコンセプトの一つである。

Componentは以下の４つの要素によって構成される。

- Componentとして宣言されるTypeScriptのクラス
    - Componentの振る舞いもここで規定される
- レンダリングを表現するHTMLのテンプレート
- 要素として別Componentから利用される際のセレクタ名
- その要素に適用されるCSSスタイルシート
    - SCSSなどのBetter CSS も利用可能

---

## `Component` の定義
`Component` も `NgModule` と同様にTypeScriptのDecoratorとして宣言され、プロパティとして以下をとることができる。

- `selector`: Componentのセレクタ名
- `template` / `templateUrl`: HTMLテンプレート、直接文字列として記述することもできるが、HTMLファイルをリンクさせるのが一般的
- `styles` / `styleUrls`: CSSスタイル、テンプレートと同様に直接文字列として記述することもできるがHTMLファイルをリンクさせるのが一般的

```ts
@Component({
  selector: "app-component-overview",
  templateUrl: "./component-overview.component.html",
  styleUrls: ["./component-overview.component.css"]
})
export class ComponentOverviewComponent {}
```

---
## Viewとデータのバインディング
Angularの特徴としてバインディング機構がある。これはクラス内で定義されているデータ及びメソッドとテンプレートを結びつける機能である。

バインディングには以下の３つのタイプがある。

- クラスからViewへの単方向
    - データをReadOnlyでView側に読み取るプロパティバインディング
- Viewからクラスへの単方向
    - Viewからクラス内のメソッドを実行するイベントバインディング
- 双方向
    - 双方向でデータを同期させる双方向バインディング
    - 実態は同名のプロパティバインディングとイベントバインディングを設定することで実現する

---
## プロパティバインディング
プロパティバインディングでは、クラスの**プロパティ**とTemplateの要素をバインディングする。プロパティなので、クラスの属性だけでなく、プロパティ記法で記載されたものも参照可能である。なお、バインディング内はTypeScriptの１行で表現できる範囲で、３項演算子￥

記法には３種類ある。

- `{{expression}}` 
    - この形式のみがInnerTextに適用できる
- `[target]="expression"`
- `bind-target="expression"`

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

@Component({
  selector: "app-component-overview",
  templateUrl: "./component-overview.component.html",
  styleUrls: ["./component-overview.component.css"]
})
export class ComponentOverviewComponent {
  message: string = "hoge"

  get message2():string {
    return "fuga"
  }
}
```

```html
<p>{{message}}{{message2}}</p>

<p [innerText]="message + message2"></p>

<p bind-innerText="message + message2"></p>
```


---
## イベントバインディング
イベントバインディングは、一般的にはクラスのメソッドを呼び出すような意図で使われる。プロパティバインディングがHTML要素の属性に対して設定していたのに対して、イベントバインディングは、onClickHなどのTML要素のイベントに対してのバインディングになる。

記法には2種類ある。

- `(target)="expression"`
- `on-target="expression"`

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

@Component({
  selector: "app-component-overview",
  templateUrl: "./component-overview.component.html",
  styleUrls: ["./component-overview.component.css"]
})
export class ComponentOverviewComponent {
  showAlert() {
    alert("Hi")
  }
}
```

```html
<button (click)="showAlert()">Click Me!!!</button>

<button on-click="showAlert()">Click Me, too!!!</button>
```

---
## 双方向バインディング
双方向バインディングは、クラスのデータと表示項目、双方向にデータが変更される。したがって、`Input` 要素などユーザーの入力を受け付けるような項目に必須となる。

記法には2種類ある。

- `[(target)]="expression"`
- `bindon-target="expression"`


```html
<h2>Dual Mode Binding</h2>

<div>
	<input type="text" [(ngModel)]=value />
</div>

<div>
  <p>{{value}}</p>
</div>
```

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

@Component({
  selector: 'app-component-overview',
  templateUrl: './component-overview.component.html',
  styleUrls: ['./component-overview.component.css']
})

export class ComponentOverviewComponent {
  value: string = "initial value"

}
```

---

## プロパティバインディング可能な属性の作成方法
自作のコンポーネントにて、プロパティバインディング可能な属性を作成するには、`＠Input`デコレータをもったプロパティを作成しするだけでよい。


```ts
  @Input() externalValue: string
```

---
## イベントバインディング可能な属性の作成方法
自作のコンポーネントにてイベントバインディング可能な属性を作成するには、 `＠Output`デコレータを持ったEventEmitterを定義し、そのイベントを発火するようにすればよい。

```ts
  @Output() buttonPush = new EventEmitter<void>();

  click() {
    this.buttonPush.emit()
  }
```

EventEmitter自体もAngularの基本機能で、Pub/Sub機構を備えたオブジェクトである。

---
## 双方向バインディング可能な属性の作成方法
双方向バインディングは、原理的にはプロパティバインディングとイベントバインディングの合成である。
つまり、 `Input（）` と `Output` の双方を同一のプロパティに対して設定してやればよい。ただし同一のメンバ名をつけることは不可能なので、 `Output` には Changedのサフィックス（接尾句）をつける。

また、`Input`をメンバ変数ではなく、getter/setterを持つプロパティとして定義しておくと、変更通知のイベントが配置しやすい。

```ts
  @Input()
  get dual(): number { return this._dual; }
  set dual(value: number) {
    this._dual = value;
    this.dualChange.emit(value)
  }
  private _dual = 0;  
  
  @Output() dualChange = new EventEmitter<number>();
```



## Demo: 実際のComponentを触ってみよう
- https://stackblitz.com/angular/mnxvjoaodbe