Angularを使ってみよう

### :point_up: はじめに
フロントのみをチュートリアルを参考にサクッと落とし込みます。
なぜAngularなのか？となりますが、癖があるので慣れると作れちゃいます。
ただ保守性が如何せん大変だということでReactのシェアが多いのだと思います。

以下の記事はよく調べられており、Angular経験者なら頷く内容でした。
[それでも Angular を選ぶ理由](https://qiita.com/luncheon/items/9dde71269335d0057844)

3年ほどまえにAngulerで[ag-grid](https://www.ag-grid.com/)を採用したことがあり、久しぶりにAngularを触ってみようと思った次第です。

### :thinking: 構成
Angular(^18.1.4) ＋ TypeScript(~5.5.2) ＋ Tailwindcss(^3.4.11)
[Angularとは？](https://angular.jp/overview)
[AngularでTailwind CSSをインストールする](https://tailwindcss.com/docs/guides/angular)

### :point_right: 1.Angular CLIをインストールする
[参考](https://angular.jp/installation)
```
$ npm install @angular/cli --no-save 
$ ng version 
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3788032/c09444c5-9a97-a881-93a4-9e472ab7456a.png)

### :point_right: 2.プロジェクト作成
私はSCSSを採用しています(お好みでよき)
```
$ ng new angular-app --directory=./
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3788032/3a359407-7f6b-2449-25f9-bf943b3ba843.png)

### :point_right: 3.Angular Material インストール 
[参考](https://material.angular.io/guide/getting-started)
選択肢が分からなければデフォルトで選択すればよいです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3788032/4007ab2b-5832-5ffd-2d30-d8fbf2f7b562.png)

### :point_right: 4.tailwindcss インストール
ここまでインストールしたら下地はできました
[参考](https://tailwindcss.com/docs/guides/angular)
```
$ npm install -D tailwindcss postcss autoprefixer 
$ npx tailwindcss init 
```
参考にあるテンプレートパスを構成してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3788032/8e4a46da-61b8-b1e7-1226-40218b593984.png)

参考にある Tailwind ディレクティブを CSS に追加してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3788032/5bdfeeb3-c8a5-e916-de1b-4b21622f9e01.png)

### :point_right: コンポーネントを追加する
よくあるヘッダ、ボディ、フッタのイメージで用意します
```
$ ng generate component header 
$ ng generate component grid 
$ ng generate component footer 
```
指定したフォルダ配下にファイルが作成されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3788032/93e3b8ad-9ca6-d610-9994-dc4f5bf31766.png)

### :point_right: ヘッダ・フッタを定義する
[テンプレート集](https://qiita.com/Masahiro111/items/f7d6ad8280ae92717f0f)があるのでイメージにあるものをチョイスする。
私は[Tailblocks](https://tailblocks.cc/)からヘッダとフッタをチョイスしました。

### :point_right: ボディを定義する（テーブルを配置します）

[CdkTable](https://material.angular.io/cdk/table/overview)
grid.component.html に以下を配置して grid.component.ts から Map を渡します。
```
<table cdk-table [dataSource]="dataSource" class="table-body">
  <ng-container cdkColumnDef="position">
    <th cdk-header-cell *cdkHeaderCellDef class="table-th">No</th>
    <td cdk-cell *cdkCellDef="let element" class="table-td"> {{element.position}} </td>
  </ng-container>
  <ng-container cdkColumnDef="name">
    <th cdk-header-cell *cdkHeaderCellDef class="table-th">名称</th>
    <td cdk-cell *cdkCellDef="let element" class="table-td underline underline-offset-4"> {{element.name}} </td>
  </ng-container>
  <ng-container cdkColumnDef="address">
    <th cdk-header-cell *cdkHeaderCellDef class="table-th">住所</th>
    <td cdk-cell *cdkCellDef="let element" class="table-td"> {{element.address}} </td>
  </ng-container>
  <tr cdk-header-row *cdkHeaderRowDef="displayedColumns"></tr>
  <tr cdk-row *cdkRowDef="let row; columns: displayedColumns;" (click)="onClickRow(row)" [style.background-color]="rowBackColor(row)"></tr>
</table>
```
[Paginator](https://material.angular.io/components/paginator/overview)
grid.component.html に以下を配置して
```
<mat-paginator #paginator
 class="demo-paginator"
 (page)="handlePageEvent($event)"
 [length]="length"
 [pageSize]="pageSize"
 [disabled]="disabled"
 [showFirstLastButtons]="showFirstLastButtons"
 [pageSizeOptions]="showPageSizeOptions ? pageSizeOptions : []"
 [hidePageSize]="hidePageSize"
 [pageIndex]="pageIndex"
 aria-label="Select page">
</mat-paginator>
```
grid.component.ts からページ情報を渡す
```
import { DataSource } from '@angular/cdk/collections';
import { BehaviorSubject, Observable } from 'rxjs';

(省略)

export interface MapsData {
  position: number;
  name: string;
  address: string;
}

(省略)

  length = 50;
  pageSize = 10;
  pageIndex = 0;
  pageSizeOptions = [5, 10, 25];

  hidePageSize = false;
  showPageSizeOptions = true;
  showFirstLastButtons = true;
  disabled = false;

  pageEvent: PageEvent | null = null;

  displayedColumns: string[] = ['position', 'name', 'address'];
  exampleDatabase = new ExampleDatabase(this.pageSize, this.pageIndex);
  dataSource: ExampleDataSource = new ExampleDataSource(this.exampleDatabase);

  ngOnInit() {
    this.dataSource = new ExampleDataSource(this.exampleDatabase);
  }
  handlePageEvent(e: PageEvent) {
    this.pageEvent = e;
    this.length = e.length;
    this.pageSize = e.pageSize;
    this.pageIndex = e.pageIndex;
    this.exampleDatabase = new ExampleDatabase(this.pageSize, this.pageIndex);
    this.dataSource = new ExampleDataSource(this.exampleDatabase);
  }
  setPageSizeOptions(setPageSizeOptionsInput: string) {
    if (setPageSizeOptionsInput) {
      this.pageSizeOptions = setPageSizeOptionsInput.split(',').map(str => +str);
    }
  }

(省略)

// Create Data
export class ExampleDatabase {
  dataChange: BehaviorSubject<MapsData[]> = new BehaviorSubject<MapsData[]>([]);
  get data(): MapsData[] { return this.dataChange.value; }

  constructor(pageSize: any, pageIndex: any) {
    for (let i = (pageIndex * 10); i < (pageSize * (pageIndex + 1)); i++) { this.addMaps(i + 1); }
  }

  addMaps(dataIndex: any) {
    const copiedData = this.data.slice();
    copiedData.push(this.createNewMaps(dataIndex));
    this.dataChange.next(copiedData);
  }

  private createNewMaps(dataIndex: any) {
    return {
      position: dataIndex,
      name: '東京駅',
      address: '東京都千代田区丸'
    };
  }
}

export class ExampleDataSource extends DataSource<any> {
  constructor(private _exampleDatabase: ExampleDatabase) {
    super();
  }
  connect(): Observable<MapsData[]> {
    return this._exampleDatabase.dataChange;
  }
  disconnect() {}
}
```
[Row Event](https://qiita.com/BlueMoonAZ/items/4e4f1d9ae163a6424e0e)
行選択時のイベントは以下のイメージです。
```
import { PageEvent, MatPaginatorModule } from '@angular/material/paginator';

(省略)

  private selectItem: any;
  onClickRow(item: any) {
    this.selectItem = item;
  }
  rowBackColor(item: any): string {
    if (this.selectItem === undefined) return 'white';
    if (this.selectItem.position === item.position) return 'lightcyan';
    return 'white';
  }
```
### :point_right: 動作確認
app.component.html にヘッダ、ボディ、フッタのコンポーネントを追加する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3788032/2a8beeb9-5545-6249-9a45-fee09634a45f.png)

```
$ ng serve --open
```
デザインが表示されれば完成です:clap:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3788032/7902380f-3bf6-4ea4-c1c4-85caee5d668f.png)

Angularを試す際の参考になればと思います:writing_hand: