この記事はIonic Advent Calendar 2018 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みたいな枠を使いたいッッッ!
こんなの↓
想定
想定としては、枠の中はion-cardと同様の使い方をしたいです。
ページのsassファイルを汚してしまうのは嫌なのでカスタムコンポーネント化します。
不規則なデザインかつ、大きさ(幅と高さ)も可変式にする必要があるのでsvgを使ってデザインを行います。
カスタムコンポーネント作成
$ ionic generate component p5-card
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編集
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編集
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編集
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で以下のように定義したデザインの条件をカスタムコンポーネントでも使えるようにします。
@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していないと、アイコンやモーダルなど影響を受けるコンポーネントは正常に動作しなくなります。
完成
 
課題
paddingの値を適当に決めてしまっているので、検証してベストな値を見つけたい。
おまけ
この記事の要領でトグルとスライダーも作ってみた。

