第1回 Angular勉強会 〜ToDoアプリ作成〜

[第1回「Webフロントエンド開発勉強会」](https://qiita.com/C3REVE/items/01cb6b9fd70f622ba622)、[第2回「Webフロントエンド開発勉強会」](https://qiita.com/C3REVE/items/fb03a4ca57b1fc246ca3) に続き、今回は Angular について説明します。

# Angular について

Angular（アンギュラー）は、Googleによって開発されているフルスタックのフレームワークで、以下のような特徴があります。

- フルスタックで、開発に必要な機能を公式で提供しているため、エンジニアは自分が開発するサービスのことだけに集中できる
- TypeScript で記述量少なく保守性の高いソースコードを書くことができる
- コンポーネント思考で、パーツごとにソースコードを分割できる
- モジュール分割して遅延ロードすることで、初期ロード時の読み込みを短縮できる
- Incremental DOM で DOM操作のパフォーマンスが良い

# 準備

## Node.js のインストール

以下から Node.js をダウンロードしてインストールしてください。
https://nodejs.org/ja/

## Angular CLI のインストール

```bash
$ npm install -g @angular/cli
```

この記事を書いたときの Angular CLI のバージョンは 6.0.8 です。

## 新しいアプリケーションの作成

```bash
$ ng new my-angular-app
```

`--routing` オプションでルーティングの設定を追加したり、`--style=scss` オプションで SCSS を指定することもできます。

ここでは以下のようにアプリケーションを作成してください。

```bash
$ ng new my-angular-app --routing
```

## ビルド＆ローカルサーバ起動

```bash
$ cd my-angular-app
$ ng serve
```

## アプリケーションの実行

Webブラウザで `http://localhost:4200` へアクセスすると起動したアプリケーションを実行できます。

また、Visual Studio Code を利用する場合は、デバッグパネルの構成を追加で `chrome` を選択し、launch.json の `url` を `"http://localhost:4200"` に変更することでデバッグ実行ができます。

```launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "chrome",
            "request": "launch",
            "name": "localhost に対して Chrome を起動",
            "url": "http://localhost:4200",
            "webRoot": "${workspaceFolder}"
        }
    ]
}
```

Visual Studio Code でのデバッグについては、以下を参照してください。
[Visual Studio Code でフロントエンドの開発環境を構築してデバッグする](https://qiita.com/C3REVE/items/273646ad028e98758e70)

## ページの追加

アプリケーションを生成した直後の状態では app.component の１ページしかありません。
この記事では各章でページを作成して動作を確認しながら進めていきたいのでページを追加します。

Angular でページの管理は ルーティング という機能で行いますが、ルーティングの説明については別の回で説明するので、ここではこんなもんだと覚えて進めてください。

angular-cli には、コードの雛形を自動生成する `generate` という機能が備わっています。
以下のコマンドを実行して index component を生成してください。

```bash
$ ng generate component index
```

※エイリアスを使用して `ng g c index` でも生成できます

同様に次の章で使用するページ `type-script` を生成してください。

ルーティングについては別の回で説明するので、ひとまず以下のファイルを変更してください。

```app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { IndexComponent } from './index/index.component';
import { TypeScriptComponent } from './type-script/type-script.component';

const routes: Routes = [
  { path: '', redirectTo: '/index', pathMatch: 'full' },
  { path: 'index', component: IndexComponent },
  { path: 'type-script', component: TypeScriptComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
```

```app.component.html
<a routerLink="/">Top</a>
<router-outlet></router-outlet>
```

```index.component.html
<h1>第１回 Angular勉強会</h1>
<ul>
  <li><a routerLink="/type-script">type-script</a></li>
</ul>
```

正しくできていればページ遷移ができていると思います。

# TypeScript

Angular の標準言語は JavaScript のスーパーセットである **TypeScript** を使用しています。

TypeScript でよく使用する構文を以下にまとめました。

```type-script.component.ts
import { Component } from '@angular/core';

/**
 * クラス <ジェネリック>
 */
class BodyContent<T> {

  /** 読み取り専用プロパティ */
  readonly createdAt: Date;

  /** 公開プロパティ */
  public updatedAt: Date;

  /** 非公開プロパティ */
  private _content: T;

  /** クラス変数 */
  static classInfo = 'BodyContent Class.';

  /** Getter */
  get content(): T {
    return this._content;
  }

  /** Setter */
  set content(value: T) {
    this.updatedAt = new Date();
    this._content = value;
  }

  /**
   * コンストラクタ
   */
  constructor(content?: T) {
    this._content = content;
    this.createdAt = new Date();
  }

  /** 公開メソッド */
  publicMethod() {

    // ブロック内のスコープの変数
    let letVal = '再代入可能';
    const constVal = '再代入不可';

    return letVal + constVal;
  }

  /** 非公開メソッド */
  private privateMethod() {
  }
}

/**
 * 抽象クラス
 */
abstract class AbstractComponent {

  body: BodyContent<string>;

  constructor(public title: string, public subTitle?: string) {
    this.body = new BodyContent();
  }
}

/**
 * インターフェース
 */
interface InterfaceComponent {

  /** プロパティ定義 */
  message: string;

  /** メソッド定義 */
  countUp();
}

@Component({
  selector: 'app-type-script',
  templateUrl: './type-script.component.html',
  styleUrls: ['./type-script.component.css']
})
export class TypeScriptComponent extends AbstractComponent implements InterfaceComponent {

  message: string;

  // 初期値を設定する場合は型を省略しても型を自動で判別する
  typeMessage = '';
  count = 1;

  // 共用型で複数指定可能（string または Array<string>）
  unionType: string | Array<string>;

  constructor() {
    super('TypeScript', '入門');

    // (引数): 戻り値型 => { 処理 } でアロー関数
    const appendMessage = (text: string): void => {
      // アロー関数の this は常に自身のオブジェクトを参照する
      this.typeMessage += text;
    }

    // テンプレート文字列
    this.message = `${this.title} ～${this.subTitle}～
テンプレート文字列は変数を埋め込めたり
改行がそのまま扱えたりします`;

    this.body.content = `はじめての ${this.title}`;

    // プリミティブ型は typeof演算子 で判別できる
    if (typeof this.count === 'number') {
      appendMessage('count は number です。');
    }
    if (typeof this.message === 'string') {
      appendMessage('message は string です。');
    }

    // どのコンストラクタから生成されたかは instanceof演算子 で判別できる
    this.unionType = [];
    if (this.unionType instanceof Array) {
      appendMessage('unionType は Array です。');
    }
    if (this.body instanceof BodyContent) {
      appendMessage('body は BodyContent です。');
    }

    // for of 文で値を取り出してループ処理
    this.unionType = ['for ', 'of ', 'loop'];
    for (let value of this.unionType) {
      appendMessage(value);
    }
  }

  // メソッドの戻り値は省略可能（return で返した値の型で自動判別）
  countUp() {
    this.body.content = `${++ this.count}回目の ${this.title}`;
    return this.body.content;
  }
}
```

```type-script.component.html
<p>{{title}}</p>
<pre>{{message}}</pre>
<div>{{typeMessage}}</div>
<div>createdAt: {{body.createdAt}}</div>
<div>updatedAt: {{body.updatedAt}}</div>
<p>{{body.content}}</p>
<div><button (click)="countUp()">Count Up</button></div>
```


# データバインディング

データバインディングは、テンプレート(html)と、コンポーネント(*.component.ts)を繋ぐ仕組みです。
コンポーネントの状態をテンプレートに伝えて表示したり、テンプレートで入力した状態をコンポーネントに伝えて処理したりすることができます。

`generate` で data-bindingコンポーネントを生成し、type-scriptコンポーネントと同様に `app-routing.module.ts` と `index.component.html` に追加してください。

```bash
$ ng generate component data-binding
```

```data-binding.component.html
<!-- Interpolation(補間) -->
<h1>{{title}}</h1>

<!-- プロパティバインディング -->
<div><img [src]="url"></div>

<!-- 属性バインディング -->
<div><input type="text" [attr.list]="datalistId"></div>
<datalist id="datalist1">
    <option>属性</option>
    <option>バインディング</option>
</datalist>

<!-- クラスバインディング -->
<p [class.red]="classRed">クラスバインディング</p>

<!-- スタイルバインディング -->
<p [style.color]="blueStyle">スタイルバインディング</p>

<!-- イベントバインディング -->
<div><button (click)="onClick()">イベントバインディング</button></div>

<!-- 双方向バインディング -->
<div><input [(ngModel)]="inputValue"></div>{{inputValue}}

<!-- テンプレート参照変数 -->
<div>テンプレート参照変数　<input #input1 (keyup)="0">{{input1.value}}</div>
```

```data-binding.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-data-binding',
  templateUrl: './data-binding.component.html',
  styleUrls: ['./data-binding.component.css']
})
export class DataBindingComponent {

  title = 'データバインディング';
  url: string;
  datalistId: string;
  classRed: boolean;
  blueStyle: string;
  inputValue: string;

  onClick() {
    this.url = 'https://qiita-image-store.s3.amazonaws.com/0/158277/profile-images/1528249969';
    this.datalistId = 'datalist1';
    this.classRed = true;
    this.blueStyle = '#0000ff';
    this.inputValue = '双方向バインディング';
  }
}
```

```data-binding.component.css
.red {
    color: red;
}
```

[イベントバインディング]ボタンをクリックすると、`data-binding.component.ts` で変更したプロパティがテンプレートに反映されることがわかります。

## データバインディングの種類

Angular のデータバインディングには以下の種類があります。

- Interpolation(補間)
- プロパティバインディング
- 属性バインディング
- クラスバインディング
- スタイルバインディング
- イベントバインディング
- 双方向バインディング


### Interpolation(補間)

コンポーネントのプロパティを、テンプレートの `{{ プロパティ名 }}` にバインドします。

```html
<h1>{{title}}</h1>
```

### プロパティバインディング

コンポーネントのプロパティを、テンプレートの要素のプロパティにバインドします。

```html
<img [src]="url">
```

### 属性バインディング

コンポーネントのプロパティを、テンプレートの要素の属性にバインドします。

```html
<input type="text" [attr.list]="datalistId">
```

### クラスバインディング

コンポーネントのプロパティを、テンプレートの要素の class にバインドします。

```html
<p [class.red]="classRed">クラスバインディング</p>
```

### スタイルバインディング

コンポーネントのプロパティを、テンプレートの要素の style にバインドします。

```html
<p [style.color]="blueStyle">スタイルバインディング</p>
```

### イベントバインディング

テンプレートのイベントを、コンポーネントのメソッドにバインドします。

```html
<button (click)="onClick()">イベントバインディング</button>
```

### 双方向バインディング

テンプレートのプロパティをコンポーネントのプロパティへ、コンポーネントのプロパティをテンプレートのプロパティへバインドします。
プロパティバインディングとイベントバインディングを組み合わせたようなバインドです。

```html
<input [(ngModel)]="inputValue"></div>
```

双方向バインディングの `ngModel` を使用するには、`FormsModule` を import する必要があります。

```app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { DataBindingComponent } from './data-binding/data-binding.component';

@NgModule({
  declarations: [
    AppComponent,
    DataBindingComponent,
  ],
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

## テンプレート参照変数

バインディング以外にテンプレートを参照する方法として、テンプレート参照変数があります。
テンプレートの要素に `#変数名` を指定することで、要素を変数として扱えるようになります。
以下は、どのタイミングでバインディングするかを `(keyup)="0"` で指定しており、場合は keyup イベントのタイミングでバインディングしています。

```html
<input #input1 (keyup)="0">{{input1.value}}
```

# パイプ

パイプ(Pipe)は、テンプレート上のデータを加工して表示する役割をします。

## 標準パイプ

Angular には予めいくつかのパイプが用意されています。

ここでは以下のパイプを使用してみましょう。

- `lowercase` 大文字を小文字に変換する
- `uppercase` 小文字を大文字に変換する
- `titlecase` 頭文字を大文字に変換する
- `slice` 文字列または配列から部分的に抜き出す
- `date` 日時のフォーマット変換
- `number` 数値をカンマ区切り表示
- `percent` 数値をパーセント表示
- `currency` 数値を通貨表示

その他の標準パイプは以下を参照してください。
https://angular.io/api?type=pipe

pipeコンポーネントを生成して `app-routing.module.ts` と `index.component.html` に追加してください。

```bash
$ ng generate component pipe
```


```pipe.component.html
<p>lowercase: {{'LOWERCASE' | lowercase}}</p>

<p>uppercase: {{'uppercase' | uppercase}}</p>

<p>titlecase: {{'titlecase' | titlecase}}</p>

<p>slice: {{'slice' | slice: 2: 5}}</p>

<p>slice(Array): {{[1, 2, 3, 4, 5, 6] | slice: 2: 5}}</p>

<p>date: {{now | date: 'yyyy/MM/dd HH:mm:ss'}}</p>

<p>number: {{10000 | number}}</p>

<p>percent: {{0.12 | percent}}</p>

<p>currency: {{1000 | currency: 'JPY'}}</p>
```

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

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

  now = new Date();
}
```

```:実行結果
lowercase: lowercase

uppercase: UPPERCASE

titlecase: Titlecase

slice: ice

slice(Array): 3,4,5

date: 2018/06/27 16:52:46

number: 10,000

percent: 12%

currency: ¥1,000
```

このように、コンポーネント側で処理を書かなくてもテンプレート側だけでデータの加工ができます。

## カスタムパイプ

標準パイプ以外にも独自でパイプを作成することもできます。

以下の `generate` で以下のようにパイプを作成できます。

```bash
$ ng generate pipe bracket
```

`transform` 関数の第一引数にはテンプレートで指定したパイプ `|` の左側の文字列が渡され、第二引数以降にはパイプの `:` の右側の引数が渡されます。

以下は、文字列をパイプの引数で渡した文字で囲う BracketPipe の例です。

```bracket.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'bracket'
})
export class BracketPipe implements PipeTransform {

  transform(value: string, start?: string, end?: string): any {
    return `${start}${value}${end || start}`;
  }

}
```

```pipe.component.html
<p>bracket: {{'ABCD' | bracket: '"'}}</p>
<p>bracket: {{'ABCD' | bracket: '{': '}'}}</p>
```

```:実行結果
bracket: "ABCD"

bracket: {ABCD}
```

## チャレンジ

文字列、または配列を反転させる `reverse` パイプを作成してください。

```html
<p>reverse(string): {{'ABCD' | reverse}}</p>

<p>reverse(Array): {{[1, 2, 3, 4] | reverse}}</p>
```

```:実行結果
reverse(string): DCBA

reverse(Array): 4,3,2,1
```

ヒント

- [reverse()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse) で配列を反転できる
- [split()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/split) で文字列を配列にできる
- [join()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/join) で配列を文字列にできる

<details><summary>答え</summary><div>

```reverse.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'reverse'
})
export class ReversePipe implements PipeTransform {

  transform(value: string | Array<any>): any {
    // 文字列の場合は配列にする
    let result = typeof value === 'string' ? value.split('') : value;
    // 反転させる
    result = result.reverse();
    // 文字列の場合は結合する
    return typeof value === 'string' ? result.join('') : result;
  }
}
```
</div></details>

# ディレクティブ

ディレクティブ(Directive)は、要素や属性を指定することでその要素に振る舞いを持たせる役割をします。

ディレクティブには、コンポーネント、属性ディレクティブ、構造ディレクティブがあり、コンポーネントは別の回で説明するので、ここでは残りの２つについて説明します。

directiveコンポーネントを生成して `app-routing.module.ts` と `index.component.html` に追加してください。

```bash
$ ng generate component directive
```

## 属性ディレクティブ

要素の属性を変更することで、要素のスタイルを変更したり、振る舞いを変更するディレクティブを属性ディレクティブといいます。

### ngClass

以下は、要素の class を変更する属性ディレクティブ `ngClass` の例です。

```directive.component.html
<p [ngClass]="pClass">ngClassディレクティブ</p>
<ul>
  <li><label><input type="checkbox" [(ngModel)]="pClass.red">Red</label></li>
  <li><label><input type="checkbox" [(ngModel)]="pClass.bold">Bold</label></li>
  <li><label><input type="checkbox" [(ngModel)]="pClass.big">Big</label></li>
</ul>
```

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

class MyClass {
  red: boolean;
  bold: boolean;
  big: boolean;
}

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

  pClass = new MyClass();
}
```

その他のディレクティブは以下を参照してください。
https://angular.io/api?type=directive

## カスタム属性ディレクティブ

ディレクティブは `ng generate directive ディレクティブ名` で作成することができます。

以下は、フォント色を変更するディレクティブの例です。

```bash
$ ng generate directive font-color
```

```font-color.directive.ts
import { Directive, Input, ElementRef, Renderer2, AfterViewInit } from '@angular/core';

@Directive({
  selector: '[appFontColor]'
})
export class FontColorDirective {

  @Input('appFontColor') fontColor: string;

  constructor(private element: ElementRef, private renderer: Renderer2) {
  }

  ngAfterViewInit() {
    this.renderer.setStyle(this.element.nativeElement, 'color', this.fontColor);
  }
}
```

```html
<p appFontColor="red">Red</p>
<p appFontColor="green">Green</p>
<p appFontColor="blue">Blue</p>
```

`selector: '[appFontColor]'` が、ディレクティブを使用するときに指定するセレクタ名、`@Input('appFontColor') fontColor: string;` が、ディレクティブに渡した引数を参照するための定義です。

`ngAfterViewInit()` は Angular のライフサイクルで、ビューが生成された直後に呼ばれるメソッドです。
ライフサイクルについては別の回で説明します。

`this.renderer.setStyle()` でスタイルの変更をしていますが、JavaScript の経験がある方はわざわざ `Renderer2` を使わなくても以下のように直接 DOMを変更しても良いのではと思った方もいるのではないでしょうか。

```ts
this.element.nativeElement.style.color = this.fontColor;
```

ネイティブ要素への直接アクセスがサポートされていない場合（Web Workerという仕組みで実行した場合等）、`nativeElement` は null となってしまうため、Angular では要素へ安全に使用できるように `Renderer2` が提供されています。
Angular で DOM操作する場合は、`Renderer2` を使用するようにしましょう。
https://angular.io/api/core/ElementRef#properties

## 構造ディレクティブ

要素を追加・除去したり、繰り返したり、DOM要素の構造を変更するディレクティブを構造ディレクティブといいます。

### ngIf

`*ngIf="プロパティ"` でプロパティの真偽値によって、要素の表示/非表示を真偽値で設定します。

```html
<input type="checkbox" #check1 (change)="0">
<span *ngIf="check1.checked">チェック中</span>
```

`*ngIf="プロパティ; else テンプレート定義名"` で、条件が false のときに表示するテンプレートの指定もできます。
テンプレート定義は `ng-template` の要素に `#定義名` で指定しますが、この `ng-template` 要素はHTMLのタグとして出力されません。

```html
<input type="checkbox" #ifCheck2 (change)="0">
<span *ngIf="ifCheck2.checked; else ifCheck2Else">チェック中</span>
<ng-template #ifCheck2Else>
  <span>チェックしてください</span>
</ng-template>
```

`*ngIf="プロパティ; then テンプレート定義名; else テンプレート定義名"` で、条件が true / false それぞれで表示するテンプレートの指定もできます。

```html
<input type="checkbox" #ifCheck3 (change)="0">
<span *ngIf="ifCheck3.checked; then ifCheck3Then else ifCheck3Else">チェック中</span>
<ng-template #ifCheck3Then>
  <span>チェック中</span>
</ng-template>
<ng-template #ifCheck3Else>
  <span>チェックしてください</span>
</ng-template>
```

### ngSwitch

`[ngSwitch]="プロパティ"` でプロパティの値によって、要素の表示/非表示を切り替えます。
`ngSwitchCase` でそれぞれの条件値を指定、`ngSwitchDefault` でそれ以外の値を設定します。

```html
必要カロリー
<select #ngSwitchSelect (change)="0">
  <option value="1">男性</option>
  <option value="2">女性</option>
  <option value="0">その他</option>
</select>
<span [ngSwitch]="ngSwitchSelect.value">
  <span *ngSwitchCase="1">2650 kcal</span>
  <span *ngSwitchCase="2">2000 kcal</span>
  <span *ngSwitchDefault>不明</span>
</span>
```

### ngFor

`*ngFor="let 変数名 of リスト"` で Iterableインターフェースを実装したリストをループして要素を表示できます。

```html
<ul>
  <li *ngFor="let num of [1, 2, 3]">{{num}}</li>
</ul>
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158277/0c17614c-ff97-2d41-793c-971bdb834866.png)

ngFor には ループ処理内で使用できる以下の変数が用意されています。

- `index` 現在のループ回数(0開始)
- `first` 最初の要素かどうか
- `end` 最後の要素かどうか
- `odd` indexが奇数かどうか
- `even` indexが偶数かどうか

```html
<ul>
  <li *ngFor="let item of items; index as i; first as first; last as last; odd as odd; even as even">
    <div>{{item}}</div>
    <span>index: {{i}}</span>
    <span *ngIf="odd"> odd </span>
    <span *ngIf="even"> even </span>
    <span *ngIf="first"> first </span>
    <span *ngIf="last"> last </span>
  </li>
</ul>
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158277/f4de842c-e960-6776-3d5f-e5137baaf80e.png)

### １つの要素に構造ディレクティブを複数指定する場合

構造ディレクティブは１つの要素に複数指定することができないため、別々の要素に指定する必要がありますが、ツリー構造を変更したくない場合は `ng-container` を使用することで、出力される HTML には影響なく指定できます。

```html
<ul class="margin-top">
  <ng-container *ngFor="let num of [0, 1, 2, 3]; odd as odd">
    <li *ngIf="odd">{{num}}</li>
  </ng-container>
</ul>
```

## カスタム構造ディレクティブ

以下は、引数に渡した日付が本日の日付の場合に要素を表示するディレクティブの例です。

```bash
$ ng generate directive ifToday
```

```if-today.directive.ts
import { Directive, Input, TemplateRef, ViewContainerRef, OnChanges } from '@angular/core';

@Directive({
  selector: '[appIfToday]'
})
export class IfTodayDirective implements OnChanges {

  @Input('appIfToday') targetDate: Date;

  constructor(private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) { }

  ngOnChanges() {
    if (this.targetDate.toLocaleDateString() === new Date().toLocaleDateString()) {
      this.viewContainer.createEmbeddedView(this.templateRef);
    } else {
      this.viewContainer.clear();
    }
  }
}
```

```directive.component.html
<p *appIfToday="date">{{date.toLocaleDateString()}} は本日です</p>
```

```directive.component.ts
  date = new Date('2018-07-03');
```

## チャレンジ

指定文字数より多く入力した場合に文字を赤く表示する属性ディレクティブ `overColor`、指定文字数を超えたら要素を表示する構造ディレクティブ `ifOverLength` を作成してください。


```html
<input type="text" [appOverColor]="{ length: 3, color: 'red' }" #text1 (keyup)="0">
<span *appIfOverLength="{ text: text1.value, length: 3 }">文字数オーバー</span>
```

- [HostListener](https://angular.io/api/core/HostListener) でイベントリスナーを設定できる
- [ngOnChanges](https://angular.io/api/core/OnChanges) でプロパティが変わったことを検知できる

<details><summary>答え</summary><div>

```over-color.directive.ts
import { Directive, Input, ElementRef, Renderer2, HostListener } from '@angular/core';

class OverColorInfo {
  length: number;
  color: string;
}

@Directive({
  selector: '[appOverColor]'
})
export class OverColorDirective {

  @Input('appOverColor') overColor: OverColorInfo;

  constructor(private renderer: Renderer2) { }

  @HostListener('keyup', ['$event.target']) onKeyup(element: HTMLInputElement) {

    if (this.overColor.length < element.value.length) {
      this.renderer.setStyle(element, 'color', this.overColor.color);
    } else {
      this.renderer.removeStyle(element, 'color');
    }
  }
}
```

```if-over-length.directive.ts
import { Directive, Input, TemplateRef, ViewContainerRef, OnChanges } from '@angular/core';

class OverLengthInfo {
  text: string;
  length: number;
}

@Directive({
  selector: '[appIfOverLength]'
})
export class IfOverLengthDirective {

  @Input('appIfOverLength') info: OverLengthInfo;

  private isShown = false;

  constructor(private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) { }

  ngOnChanges() {
    if (!this.isShown && this.info.length < this.info.text.length) {
      this.viewContainer.createEmbeddedView(this.templateRef);
      this.isShown = true;
    } else if (this.isShown && this.info.length >= this.info.text.length) {
      this.viewContainer.clear();
      this.isShown = false;
    }
  }
}
```

</div></details>

# ToDoアプリ作成

[第1回「Webフロントエンド開発勉強会」](https://qiita.com/C3REVE/items/01cb6b9fd70f622ba622) で作成したTODOアプリを Angular で実装してください。

## 追加１ タスク作成日時の表示

削除ボタンの右側に、タスク作成日時を `2018/07/03 15:00:00` の形式で表示してください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/158277/8b6ca0d9-f8d9-20c6-e516-0e4fa799498c.png)

## 追加２ 非表示ボタン

非表示ボタンを押したらタスクを非表示にし、全て表示ボタンを押したら再表示してください。

![step2.gif](https://qiita-image-store.s3.amazonaws.com/0/158277/fd271be1-467d-0277-4f25-f349b7baa1da.gif)