Angularアプリケーションをブラウザのコンソールからデバッグする

# 前提
本記事の内容は主にAngular v9以上を対象としています。
v9よりIvy（Angularの次世代レンダリングエンジン）がデフォルトで有効になり、Ivyの導入に伴いデバッグAPIの仕様が変更されています。
そのため、v8以前では本記事で紹介するデバッグAPIは利用できません。

# ブラウザコンソールからAngularの状態を見る
Angularの開発モード（`ng serve`した状態）では、@angular/core/globalパッケージを使用してコンソールからアプリケーションの状態を見ることができます。
@angular/core/globalパッケージのメンバは、`ng`という名前空間に属する関数としてグローバル領域（windowオブジェクト内）に公開されます。
コンソールより`ng.XXXX`と関数を実行することで、コンポーネントのインスタンスなどを取得できます。

<img width="1248" alt="window.ng.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/460735/298f6175-0c05-512e-3815-0d9db5344cd7.png">

以下は使用可能な関数の一覧です。[公式APIガイド](https://angular.io/api/core/global)より抜粋（意訳）したものです。

|関数|内容|
|---|---|
|ng.applyChanges|引数として渡したコンポーネントが属するアプリケーションの変更検知を実行します。(コンポーネントがOnPushの場合、チェック対象としてマークしたうえで実行します。）|
|ng.getComponent|引数として渡したDOM要素に関連付けられているコンポーネントインスタンスを取得します。|
|ng.getContext|引数として渡したDOM要素が埋め込みビュー内（*ngIfや*ngFor）にある場合、その要素が属するコンテキストを取得します。それ以外の場合、引数のDOM要素が属するコンポーネントのインスタンスを取得します。（getOwningComponentを実行したときと同じ）|
|ng.getDirectives|引数として渡したDOM要素に関連付けられているディレクティブインスタンスを取得します。コンポーネントインスタンスは含まれません。|
|ng.getHostElement|引数として渡したコンポーネントインスタンスまたはディレクティブインスタンスのホスト要素を取得します。|
|ng.getInjector|引数として渡したDOM要素、コンポーネントインスタンス、ディレクティブインスタンスに関連付けられたインジェクタを取得します。|
|ng.getListeners|引数として渡したDOM要素に関連付けられたイベントリスナを取得します。ホストリスナを含みますが、Angularコンテキスト外で定義されたイベントリスナ（たとえばaddEventListenerを介したもの）は含まれません。|
|ng.getOwningComponent|引数として渡したDOM要素をビューに含むコンポーネントインスタンスを取得します。|
|ng.getRootComponents|引数として渡したDOM要素、コンポーネントインスタンス、ディレクティブインスタンスに関連付けられたルートコンポーネントを取得します。|

# ユースケース
@angular/core/globalパッケージ（`ng.XXXX`）を使って実際にどのようなことができるのか、ユースケースを見ていきましょう。

## 1. バインドする値を変更してビューに反映させる
プロパティバインディングやInterporationなどでコンポーネントインスタンスの変数をビューにバインドしている場合、コンソールを使ってインスタンスの変数を操作し、ビューを変化させることができます。

<img width="762" alt="bind.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/460735/b374690b-9f5a-8043-c747-c14424af295a.png">

たとえば、上記の例では`AppComponent`の`title`文字列がInterporationでビューに表示されています。
これを以下のようにコンソールから操作します。

1. DevToolsのインスペクタまたはElementsタブで要素を指定する
   <img width="1248" alt="inspector.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/460735/f6d7eeb1-996a-322d-4257-0370696dec91.png">
2. Consoleタブで以下のコマンドを入力

```javascript
// 要素の属するコンポーネントインスタンスを取得
// ※DevToolsでは選択した要素は「$0」変数に格納されている
const cmp = ng.getOwningComponent($0);

// インスタンスの変数を変更
cmp.title = 'Change value from console';

// ビューの変更検知を実行
ng.applyChanges(cmp);
```
<img width="1248" alt="change-value.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/460735/a6882a3e-1cf4-69d0-bb83-23a43e6ed9b1.png">

本来ならアプリケーションに実装された正規の手続きを踏んでtitleを変更するべきですが、単純にビューの見た目のチェックをする程度であれば十分有用でしょう。

## 2. ngForでループされている内容を確認する
ngForは配列などを渡すと内容を展開してビューを形成してくれる便利なディレクティブですが、実際にどのようなデータがngForに渡っているか、見えているビューからすべてを把握することが難しい場合があります。

<img width="958" alt="ngFor.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/460735/1a8678e9-e3f2-21e0-7fff-59bb08c4d464.png">

このような場合、以下のようにしてngForで処理された内容を伺うことができます。

1. DevToolsのインスペクタまたはElementsタブでngForによって生成された要素を指定する
2. Consoleタブで以下のコマンドを入力

```javascript
// 要素の属するコンテキストを取得
// ※DevToolsでは選択した要素は「$0」変数に格納されている
ng.getContext($0);
```

<img width="1248" alt="getContext.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/460735/c403ef17-4fb0-054c-d36a-c228ec47b259.png">

上記の例では一つ目のdiv（配列の最初の要素＝`aaa`）を選択しています。
`getContext`の戻り値からは`NgForOfContext`型のオブジェクトが返され、ngForに渡ったデータのほか、`index`、`count`、`first`、`odd`といった情報を確認することができます。

## 3. 任意のイベントを発火させる
Angularでは、子コンポーネントから親コンポーネントへデータを渡す場合、子コンポーネントのEventEmitterを発火させ、コールバックという形で受け渡します。

```html:app.component.html
<app-child (someEvent)="onReceiveValue($event)"></app-child>
```

```typescript:app.component.ts
onReceiveValue(e: string): void {
  // ここで何らかの処理
  console.log('event callback');
  console.log(e);
}
```

以下のようにすると、子コンポーネントの実装によらずにイベント発生を再現し、親コンポーネントのコールバックを実行することができます。

1. DevToolsのインスペクタまたはElementsタブで子コンポーネント内の要素を指定する
2. Consoleタブで以下のコマンドを入力

```javascript
// 要素の属するホスト要素（app-child要素）を取得
// ※DevToolsでは選択した要素は「$0」変数に格納されている
const cmp= ng.getOwningComponent($0);
const host = ng.getHostElement(cmp);

// ホスト要素（app-child要素）のイベントリスナを取得
// ※リスナは配列で返却される
const lsnr = ng.getListeners(host);

// イベントリスナのコールバックを実行
lsnr[0].callback('some value from child component');
```

<img width="1248" alt="event-emitter.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/460735/c00d8274-4292-3f02-187d-ba8a3837eb8b.png">

もちろん、親子間のEventEmitter発火のみでなく、通常のクリックイベントなどでも同様にしてコールバックを実行させることができます。

# 参考
[Angular APIガイド: @angular/core/global](https://angular.io/api/core/global)
[Debugging Angular Ivy Applications from the Devtools Console](https://dev.to/angular/debugging-angular-ivy-applications-from-the-devtools-console-415j)
[Console Debugging with `window.ng`](https://brianflove.com/2019/12/14/console-debugging-with-window-ng/)