個人開発でよく使うWebアプリのデザイン・実装 (Ionic / Angular)

# はじめに

Ionicを使ってWebアプリを作る上で、個人開発でよく使うデザイン・実装をまとめてみました。
実装例には全てIonicのデフォルトコンポーネントを使っているので、追加ライブラリのインストールは不要です！

タイトル横の星の数はおすすめ度です。(個人的な印程度に思ってください)

## Ionicとは

https://ionicframework.com/

Ionicは、通常のWebページはもちろん、PWAやハイブリットアプリも同一のソースコードから生成できる、とても便利なフロントエンドのフレームワークです。
少ないリソースで多くのプラットフォームに対応したアプリケーションを作成できるので、個人や小規模などのリーンな開発で特に有用かと思います。

いい感じの見た目のUIコンポーネントが予め備わっているので、CSSフレームワークの選定をせず、すぐに開発を始められるので、開発を始めるまでに迷うことが少ないです。
なのでフロントエンドが主戦場じゃない人でも親しみやすいと思います。(自分自身も普段の業務ではサーバーサイドエンジニアとして開発しています)

## 実装例のバージョン

実装例のバージョンはIonic4(4.0.0-beta.16)、Angular6(6.1.1)です。
特にIonicは、4未満のバージョンでは属性が結構変わると思うので、注意してください。

# デザイン・実装パターン

## いいねボタン   <font color="#74C13A">★☆☆</font>

一般的なSNSでよく使われている機能なので、それにデザインを寄せてユーザーが直感的に触れるようにします。

いいね前 <img width="152" alt="スクリーンショット 2018-12-03 13.23.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/67773/3612aa34-3808-6b18-4709-fc06fbc3084a.png"> いいね後 <img width="146" alt="スクリーンショット 2018-12-03 13.23.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/67773/8a119ed9-d90f-9abc-2ade-ac6e76f3bea1.png">

***

**実装例**

* ion-buttonを使う。
* Ionicのデフォルトの色設定を使わない場合は、 [ここ](https://beta.ionicframework.com/docs/theming/advanced#adding-colors) を参考に新しい色を定義する。
  
```:html
<ion-button color="danger"
            shape="round"
            fill="outline"
            *ngIf="liked"
            (click)="removeLike()">
  <ion-icon name="heart"　padding-end></ion-icon>
  {{likeCount}}
</ion-button>
<ion-button color="medium"
            shape="round"
            fill="outline"
            *ngIf="!liked"
            (click)="like()">
  <ion-icon name="heart" padding-end></ion-icon>
  {{likeCount}}
</ion-button>
```

## 利用規約 <font color="#74C13A">★☆☆</font>

![2018-12-05 09.26.39.gif](https://qiita-image-store.s3.amazonaws.com/0/67773/de3e21fa-9c01-9c24-b015-4ee4a0f30d89.gif)

***

**実装例**

- ion-textareaを使う。
- 規約本文を改行させるため、`white-space: pre-wrap;`を追加する。
- 規約本文は長いので別ファイルにして、それを読み込ませて、`{{acceptTerm}}` にバインドする

```:html
<ion-textarea readonly
              style="white-space: pre-wrap; font-size: 0.8rem; background-color: #eeeeee;"
              value="{{acceptTerm}}">
</ion-textarea>
```

## ヘッダー左端のロゴをクリックして、ホーム画面に飛ぶ <font color="#74C13A">★☆☆</font>

![2018-12-05 08.07.53.gif](https://qiita-image-store.s3.amazonaws.com/0/67773/dfee9edc-068f-5404-cb99-34fe952a9b0d.gif)

***

**実装例**

- ion-buttonを使う。
- `routerLink="/"`の設定をする

```
<ion-app>
  <ion-header>
    <ion-toolbar color="primary" no-border-bottom>
      <ion-buttons>
        <ion-button routerLink="/">
          <img src="hogehoge" style="height: 40px;">
        </ion-button>
      </ion-buttons>
    </ion-toolbar>
  </ion-header>
</ion-app>
```

## ヘッダーの右端のアバターをクリックして、右端からメニューを表示する  <font color="#74C13A">★☆☆</font>

SNSによくある、アバターをクリックするとアカウント設定や、ログアウト、退会などを表示する機能です。

![2018-12-05 08.09.41.gif](https://qiita-image-store.s3.amazonaws.com/0/67773/8147d99a-333a-8a79-09d3-fd532bdb26ee.gif)

***

**実装例**

- ion-menuを使う
- 画面右端から出すので`side="end"` を追加する
- エフェクトはお好みで。今回は`type="reveal"`にした

```:html
<ion-app>
  <ion-menu #menu side="end" type="reveal">
    <ion-header>
      <ion-toolbar></ion-toolbar>
    </ion-header>
    <ion-content>
      <ion-list>
        <ion-item (click)="logout()">
          <ion-icon name="exit" padding-end></ion-icon> ログアウト
        </ion-item>
        <ion-item (click)="unsubscribed()">
          <ion-icon name="walk" padding-end></ion-icon> 退会
        </ion-item>
      </ion-list>
    </ion-content>
  </ion-menu>
  <ion-header>
    <ion-toolbar color="primary" no-border-bottom>
      <ion-buttons slot="end">
        <ion-avatar
                (click)="toggleMenu()"
                style="width: 40px; height: 40px;">
          <img [src]="getProfileUrl()">
        </ion-avatar>
      </ion-buttons>
    </ion-toolbar>
  </ion-header>
  <ion-router-outlet main></ion-router-outlet>
</ion-app>
```

```ts:ts
@ViewChild('menu') menu

toggleMenu() {
    this.menu.toggle()
}
```

## おしゃれなボタン <font color="#74C13A">★☆☆</font>

とりあえずボタンをこれにするとおしゃれ度が増した気になります。(デザイナーの人に怒られそうな発言)

デフォルト(変更前)<img width="182" alt="スクリーンショット 2018-12-03 13.05.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/67773/b949a007-aafd-55d7-396e-21bee8120047.png"> 変更後 <img width="214" alt="スクリーンショット 2018-12-03 13.02.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/67773/ad7a96f4-4ac2-efa0-0150-341de4c6f87a.png">

***

**実装例**

- ion-buttonを使う
- fillをoutlineにして、shapeをroundにする。

```
<ion-button
        routerLink="/search"
        float-right
        fill="outline"
        shape="round">
  <ion-icon name="search" padding-end></ion-icon>
  もっと見る
</ion-button>
```

## ボタンを右に寄せる <font color="#74C13A">★☆☆</font>

例えばこの編集ボタンみたいなデザインです。

<img width="778" alt="スクリーンショット 2018-12-03 13.47.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/67773/1190d7aa-407d-937b-dc27-dbaa6fc6b241.png">

***

**実装例**

- ion-buttonを使う
- `float-right`を設定する

```
<ion-button (click)="updateProject()"
            float-right
            fill="outline"
            shape="round"
            *ngIf="isEditable()">
  <ion-icon name="create" padding-end></ion-icon>
  編集
</ion-button>
```

## レスポンシブ対応を最低限に抑える <font color="#74C13A">★★☆</font>

PC表示のときはoffsetを追加し、スマホ表示のときはoffsetなしにします。

これは、そもそもPCのときの表示領域を狭めれば、スマホ表示時でのギャップが少なくなり、スマホでの見た目があまり崩れないので、スマホ対応に割く時間を少なくするという戦法です。少ないリソースでPCとスマホの両対応をしたいときに使うと良いです。

PC
<img width="1206" alt="スクリーンショット 2018-12-03 13.41.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/67773/77cdae0a-a104-3084-47a2-31691bd6af9f.png">

スマホ
<img width="281" alt="スクリーンショット 2018-12-03 13.42.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/67773/9fed1881-6eaf-3290-83eb-34a1f33cff31.png">

***

**実装例**

対象の`ion-col`に `offset-md="2" size-md="8" size="12"` の設定を追加。

```
<ion-content>
  <ion-grid>
    <ion-row>
      <ion-col offset-md="2" size-md="8" size="12">
        <app-product-basic-info
                [productId]="productId">
        </app-product-basic-info>
      </ion-col>
    </ion-row>
  </ion-grid>
</ion-content>
```


## ユーザーID登録時のID重複チェックの非同期処理 <font color="#74C13A">★★★</font>

![2018-12-05 08.25.57.gif](https://qiita-image-store.s3.amazonaws.com/0/67773/95f280c7-0c32-8503-13f9-b88b82ed34a1.gif)

***

**実装例**

- ion-inputを使う
- ionChangeイベントを使う
- `debounce: 200;`を追加して、リクエストを間引く

```
<ion-col size="8">
  <ion-item>
    <ion-label position="stacked">ユーザーID</ion-label>
    <ion-input [(ngModel)]="userProfile.id" (ionChange)="checkExistUserId()" debounce="200"></ion-input>
  </ion-item>
</ion-col>
<ion-col size="4" align-self-center>
  <ion-text color="danger" *ngIf="!isUniqueUserId">
    登録済みのIDです。
  </ion-text>
  <div *ngIf="isUniqueUserId">
    <ion-icon name="checkmark-circle" color="primary" size="large"></ion-icon>
  </div>
</ion-col>
```

## スライドとそれを動かすボタン <font color="#74C13A">★★★</font>

スマホはスワイプ、PCはボタンで操作するための実装です。

PC ![2018-12-05 08.10.44.gif](https://qiita-image-store.s3.amazonaws.com/0/67773/65b09ff2-b122-8afa-98ce-dd16919b6257.gif)

スマホ ![2018-12-05 08.12.54.gif](https://qiita-image-store.s3.amazonaws.com/0/67773/a7e7f803-8e37-99c8-7fcb-f08b5b0b1951.gif)

***

**実装**

- ion-slides を使う
- PC表示時のボタンは、見た目がいい感じのion-fab-buttonのminiサイズを使う
- PC表示時のボタンは、ion-slidesに重ねるため、`position: absolute;` を追加する。親要素には `position: relative;` を追加する

```:html
<div style="position: relative;">
  <ion-slides #slides [options]="slideOption">
    <ion-slide *ngFor="let product of products">
      <app-product-card [product]="product" style="width: 100%; "></app-product-card>
    </ion-slide>
  </ion-slides>
  <div style="width: 100%; position: absolute; top: 50%; z-index: 2;" *ngIf="!display.isSm() && !!products.length">
    <ion-fab-button color="primary"
                    style="margin-top: -1.5em;"
                    mini
                    float-left
                    (click)="prev()">
      <ion-icon name="arrow-dropleft" size="large">
      </ion-icon>
    </ion-fab-button>
    <ion-fab-button color="primary"
                    style="margin-top: -1.5em;"
                    mini
                    float-right
                    (click)="next()">
      <ion-icon name="arrow-dropright" size="large">
      </ion-icon>
    </ion-fab-button>
  </div>
</div>
```

```ts:ts
@ViewChild('slides') slides: Slides;

next() {
    this.slides.slideNext();
}

prev() {
    this.slides.slidePrev();
}
```

# まとめ

繰り返しになりますが、全てIonicのデフォルトコンポーネントを使っているので、追加ライブラリのインストールは不要です。
Ionicは個人開発や小規模チームの開発などの少ないリソースで、早さ優先でリリースしたいときなどに本当に便利なので、ぜひ使ってみてください！