Ionicのカスタムコンポーネントでペルソナ5のUIを再現してみた

この記事は[Ionic Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ionicframework) 18日目の記事になります。

今年になってIonicを触り始め、技術書典5にもIonicの本を出させてもらいました。
この記事では、カスタムコンポーネントで神ゲーであるペルソナ5のフレームUIを再現します。

## 開発環境
* Ionic
    * Ionic CLI : 4.1.0
    * Ionic Framework : ionic-angular 3.9.2
    * @ionic/app-scripts : 3.2.1

* System
    * NodeJS : v10.9.0
    * npm : 6.2.0
    * OS : macOS High Sierra


## Ionicでペルソナ5みたいな枠を使いたいッッッ！
こんなの↓

![window_S_LW.png](https://qiita-image-store.s3.amazonaws.com/0/319724/4faf9c71-13d5-13c5-f5ed-5f3f2fefb2cd.png)


### 想定
想定としては、枠の中は`ion-card`と同様の使い方をしたいです。
ページのsassファイルを汚してしまうのは嫌なのでカスタムコンポーネント化します。
不規則なデザインかつ、大きさ(幅と高さ)も可変式にする必要があるのでsvgを使ってデザインを行います。

## カスタムコンポーネント作成
```shell-session
$ ionic generate component p5-card
```

## htmlテンプレート編集
```html
<ion-card id="p5-card" (tap)="p5TapEvent()" no-margin>
    <!-- 外側の黒い枠　-->
    <svg class="p5-card-frame" viewBox="0,0,100,100" width=100 height=100 preserveAspectRatio="none" fill="black">
        <polygon points="0,95 90,100 100,0 5,10"></polygon>
    </svg>
　　　　　　　　<!-- 内側の白い枠　-->
    <svg class="p5-card-frame" viewBox="0,0,100,100" width=100 height=100 preserveAspectRatio="none" fill="white">
        <polygon points="5,90 89,95 95,5 7,15"></polygon>
    </svg>
    <!-- <p5-card>ここがng-contentとして挿入される</p5-card> -->
    <ng-content></ng-content>
</ion-card>
```
やっていることは、`<ion-card>`の幅と高さを100として計算し、svgで枠の形のボックスを挿入しているだけです。
こうすれば、`ion-card`の恩恵を受けつつ枠を作ることができます。

## .scss編集
```sass
p5-card {
    .p5-card-frame {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height:100%;
        z-index: -1;
    }
    #p5-card {
        position: relative;
        background-color: rgba(0,0,0,0);
        border: none !important;
        box-shadow:none;
        padding: 15px 30px 15px 20px;
        text-align: center;
        width: 100%;
    }
}
```

## .ts編集
```typescript
import { Component, Output, EventEmitter } from '@angular/core';

@Component({
    selector: 'p5-card',
    templateUrl: 'p5-card.html'
})
export class P5CardComponent {
    @Output() p5Tap = new EventEmitter();
    constructor() {
    }
    p5TapEvent() {
        this.p5Tap.emit();
    }
}
```
アウトプットイベントに`p5Tap`というイベントを作成しました。
このように、Outputデコレータを使えばカスタムコンポーネント独自のイベントを作ることができます。

## 問題発生
このまま`ionic serve`してもエラーが出ます。
標準では、カスタムコンポーネント内では`ion-*`といったIonicのコンポーネントを使うことができません。
では、使える使えるようにしましょう。
`components.module.ts`を編集します。

## components.module.ts編集
```typescript
import { NgModule, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from 'ionic-angular';
import { P5CardComponent } from './p5-card/p5-card';
@NgModule({
	declarations: [P5CardComponent,
	],
	imports: [CommonModule,
            IonicModule],
	exports: [P5CardComponent,
	],
	schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class ComponentsModule {}
```

ここで新たにimportしたのは

* P5CardComponent
* CUSTOM_ELEMENTS_SCHEMA
* CommonModule
* IonicModule

P5CardComponentは言うまでもなく、先ほど作成したカスタムコンポーネントです。

## CUSTOM_ELEMENTS_SCHEMA
まずschemasと言うのは、
>Angularのコンポーネントやディレクティブではない、Angular外で定義された要素とプロパティをHTMLパーサがどのように取り扱うか指定するもの

`CUSTOM_ELEMENTS_SCHEMA`は、
>Angularの標準コンポーネント以外をカスタムコンポーネント内で使っても認識するようにするもの

こうすることで、カスタムコンポーネント内で`ion-*`系のコンポーネントや自分で定義したコンポーネントを使えるようになります。

## CommonModule
`CommonModule`は
>カスタムコンポーネント内で、Angular標準のDirectiveやPipeを使えるようにするもの

つまり、

* ngIf
* ngFor
* ngSwitch

などの構造Directiveや

* AsyncPipe
* DecimalPipe

などのPipeをカスタムコンポーネントで使えるようになります。

## IonicModule
`app.module.ts`で以下のように定義したデザインの条件をカスタムコンポーネントでも使えるようにします。

```typescript
@NgModule({
  declarations: [ MyApp ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp, {
      backButtonText: 'Go Back',
      iconMode: 'ios',
      modalEnter: 'modal-slide-in',
      modalLeave: 'modal-slide-out',
      tabsPlacement: 'bottom',
      pageTransition: 'ios-transition'
    }, {}
  )],
  bootstrap: [IonicApp],
  entryComponents: [ MyApp ],
  providers: []
})
```

IonicModuleをimportしていないと、アイコンやモーダルなど影響を受けるコンポーネントは正常に動作しなくなります。

## 完成
<img width="375" alt="localhost_8100_(iPhone X).png" src="https://qiita-image-store.s3.amazonaws.com/0/319724/e8995d51-ddd2-b43e-83da-b14ef6aa57b7.png">

## 課題
paddingの値を適当に決めてしまっているので、検証してベストな値を見つけたい。

## おまけ
この記事の要領でトグルとスライダーも作ってみた。

![Untitled.gif](https://qiita-image-store.s3.amazonaws.com/0/319724/0025641b-b273-469e-6587-3b8b9d38e386.gif)