Angular v7のDragDropModuleを使ってTrelloっぽいの作ってみた

Angular 7.0.0がリリースされましたね 🎉
CDKにDrag and DropのModuleが追加されたので触ってみました。

ドラッグ・アンド・ドロップの機能って自前で開発すると結構めんどくさかったのが、めちゃくちゃ簡単に実装できました👏楽しい👏
[DEMO](https://angular-like-trello.stackblitz.io)

## やったこと

- `app.module.ts` で `DragDropModule` をimportする 

```typescript:app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { DragDropModule } from '@angular/cdk/drag-drop';

import { AppComponent } from './app.component';

@NgModule({
  imports: [
    BrowserModule,
    DragDropModule
  ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }
```

- `app.component.ts` で `CdkDragDrop, moveItemInArray, transferArrayItem` をimportする
- ドロップ時のメソッドを定義する
    - event発生したときに、 `container` が移動前と同じ配列か違う配列かを判定して、 `moveItemInArray()` か `transferArrayItem()` を実行する

```typescript:app.component.ts
import { Component } from '@angular/core';
import { 
  CdkDragDrop, 
  moveItemInArray, 
  transferArrayItem 
} from '@angular/cdk/drag-drop';


@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  public todo = [
    '働く',
    '映画を見る',
    '掃除する',
    '洗濯する',
    '飲みに行く',
    '買い物に行く',
    '家に帰る',
    '散歩する',
  ]

  public doing = [
    'サッカーする',
    '野球する',
    'バスケする',
    'テニスする',
  ]

  public done = [
    '本を読む',
    '歯を磨く',
    '昼寝する',
    'メールチェック',
    '犬と遊ぶ'
  ]

  public drop(event: CdkDragDrop<string[]>): void {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data,
                      event.previousIndex,
                      event.currentIndex);
    } else {
      transferArrayItem(event.previousContainer.data,
                        event.container.data,
                        event.previousIndex,
                        event.currentIndex);
    }
  }
}
```

- `cdkDropList` を `cdkDrag` 要素セットの親要素に追加する
- `#todoList="cdkDropList"` ←のように `cdkDropList`への参照をテンプレート変数化する
- `[cdkDropListConnectedTo]="[doneList]"`
    - 移動先としてつなぎたい `cdkDropList` の参照を指定する
- `cdkDropListDropped` イベントは、ドラッグが終了した時に発火するので、 `app.component.ts` で定義しておいた `drop()` メソッドを使う

```html:app.component.html
<section class="container">
  <h1>MY TASKS</h1>
  <div class="cards">
    <section class="card">
      <h2>
        TODO
      </h2>
      <ul cdkDropList 
          #todoList="cdkDropList"
          [cdkDropListData]="todo"
          [cdkDropListConnectedTo]="[doingList, doneList]"
          class="list"
          (cdkDropListDropped)="drop($event)">
        <li class="item" *ngFor="let item of todo" cdkDrag>{{item}}</li>
      </ul>
    </section>
    <section class="card">
      <h2>
        DOING
      </h2>
      <ul cdkDropList 
          #doingList="cdkDropList"
          [cdkDropListData]="doing"
          [cdkDropListConnectedTo]="[todoList, doneList]"
          class="list"
          (cdkDropListDropped)="drop($event)">
        <li class="item" *ngFor="let item of doing" cdkDrag>{{item}}</li>
      </ul>
    </section>
    <section class="card">
      <h2>
        DONE
      </h2>
      <ul cdkDropList 
          #doneList="cdkDropList"
          [cdkDropListData]="done"
          [cdkDropListConnectedTo]="[todoList, doingList]"
          class="list"
          (cdkDropListDropped)="drop($event)">
        <li class="item" *ngFor="let item of done" cdkDrag>{{item}}</li>
      </ul>
    </section>
  </div>
</section>
```

## さいごに
実際の機能開発だと、もっとやることあると思いますが、とりあえずUIはこれだけで実装できました。
ライブラリとか使ったり、自前で開発する必要がなさそうなので、とても良いですね。


## cf.
- https://medium.com/angular-japan-user-group/version-7-of-angular-cli-prompts-virtual-scroll-drag-and-drop-and-more-a78c65a5d575
- https://material.angular.io/cdk/drag-drop/overview