【Angular Tips】Angular Materialのローディングスピナーを使ってみる 

# はじめに

時間のかかるAPIを呼び出す場合等ユーザーに処理中であることを通知したい場合、1つの案としてローディングスピナーが挙げられます。
ローディングスピナーには以下のような効果があります。

* ユーザーに処理中であることを通知することによるユーザビリティ向上
* 処理中にユーザーが他の操作をすることの防止
* ボタン連打による二重処理の防止

今回は、Angular公式から出ているUIライブラリ-[Angular Material](https://material.angular.io/)を使ってローディングスピナーを実装したいと思います。


# Angular Materialのインストール

インストールにはAngular CLIのコマンド```ng add```を使います。

```
ng add @angular/material
```

詳細は[公式ガイド](https://material.angular.io/guide/getting-started)に記載の通りです。

# モジュールのインポート

モジュールに[MatProgressSpinnerModule](https://material.angular.io/components/progress-spinner/overview)をインポートする。

```typescript:app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; //<--追加

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { LoadingSpinnerComponent } from './component/loading-spinner.component';
import { LoadingSpinnerCustomComponent } from './component/loading-spinner-custom.component';

@NgModule({
  declarations: [
    AppComponent,
    LoadingSpinnerComponent,
    LoadingSpinnerCustomComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MatProgressSpinnerModule　 //<--追加
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

# ローディングスピナーの実装

ローディングスピナー用のコンポーネントとその表示を制御するサービスクラスを作っていく。

```typescript:loading-spinner.component.ts
@Component({
  selector: 'app-loading-spinner',
  templateUrl: './loading-spinner.component.html',
  styleUrls: ['./loading-spinner.component.css']
})
export class LoadingSpinnerComponent {

  /** スピナーの表示、非表示フラグ */
  public isLoading: Subject<boolean> = this.loadingSpinnerService.isLoading;

  constructor(private loadingSpinnerService: LoadingSpinnerService) {}

}
```

```html:loading-spinner.component.html
<div *ngIf="isLoading | async" class="overlay">
    <mat-progress-spinner class="spinner" mode="indeterminate"></mat-progress-spinner>
</div>
```

mat-progress-spinnerタグのmodeに指定しているのはローディングスピナーの形式。
以下の2種類が用意されている。

* determinate（デフォルト）:指定した%の進行状況を表示する形式
* indeterminate:スピナーが常に周り続ける形式

determinateは進行状況の更新等少々処理が多くなるので、indeterminateを使っていく。

```typescript:loading-spinner.service.ts
@Injectable({
  providedIn: 'root'
})
export class LoadingSpinnerService {
  /** スピナーの表示、非表示フラグ */
  public isLoading: Subject<boolean> = new Subject<boolean>();

  constructor() { }

  /**
   * スピナーの表示
   */
  public show(): void {
    this.isLoading.next(true);
  }
  
  /**
   * スピナーの非表示
   */
  public hide(): void {
    this.isLoading.next(false);
  }
}
```

スピナーの表示、非表示を行う際は、以下のようにサービスクラスのshow,hide関数をコールする。
非表示処理をfinalizeの中に実装しているのは、APIが正常、エラー応答どちらの場合でも解除したいため。

```typescript:app.component.ts
  onClick(){
    // スピナー表示
    this.loadingSpinnerService.show();
    
    // API呼び出しの仮実装(5秒間待機する)
    of().pipe(delay(5000),finalize(()=>{
      // スピナー非表示
      this.loadingSpinnerService.hide();
    })).subscribe();
  }
```

実装すると以下のようにボタンを押下するとローディングスピナーが表示されるようになる。

![スピナー.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498430/ff571f5c-a12f-7f1e-0c53-d9ee3e8b9ee6.gif)


# ローディングスピナーのカスタマイズ

Angular Materialではローディングスピナーのデザインとしては上記の1通りしか用意がされていません。
(色やサイズについては設定で変更可能)
そのため、独自のデザインを反映したい場合はローディングスピナー用のコンポーネントに手を入れることで実現します。
結構cssをいじるだけで色々な動きをさせることができるようで面白かったです。
ネット上に転がっている無料のサンプルの中から、今回は[three-dots](https://github.com/nzbin/three-dots)というものを使ってみました。
![スピナーカスタム.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498430/5a6b8963-f512-4c96-1965-8c5f7bee7eea.gif)

# まとめ

今回はAngular Materialを使ってローディングスピナーを実装してみました。
Angular Materialはこれに限らず様々なUI部品が揃っているので、他のものも使ってみたいです。

今回のサンプルは[Github](https://github.com/Yoshi034/angular-loading-spinner-sample.git)に公開しています。

余談ですが、UIライブラリも公式から出ているのがVue.jsやReactと違って面白いなと感じました。
インストールも簡単ですし、ガイドも充実しているので(日本語版はないですが...)マテリアルデザインをサクッと作ることができます。
注意点として、Angularのアップデート時にはAngular Materialのアップデートも必要です。
Angularは半年に1回のペースで新バージョンがリリースされますが、同タイミングでAngular Materialもリリースされます。