中安拓也のブログ

プログラミングについて書くブログ

【Angular】Akita学習(1) - カウンターアプリ作成

はじめに

仕事で状態管理ライブラリのAkitaを使うことになったので勉強のためにAngularとAkitaを使ってカウンターアプリを作ることにしました。

Akitaって?

f:id:l08084:20201005162817p:plain

Angular, React, Vueに対応している状態管理ライブラリになります。

f:id:l08084:20201006221324p:plain

状態を保持するStore、現在の状態を取得するQuery、状態を更新したり、APIを呼び出したりするServiceという風に機能が分かれています。

環境

  • Angular: 8.2.14
  • @datorama/akita: 5.2.4

$ ng versionの実行結果

Angular CLI: 8.3.29
Node: 12.13.1
OS: darwin x64
Angular: 8.2.14
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.803.29
@angular-devkit/build-angular     0.803.29
@angular-devkit/build-optimizer   0.803.29
@angular-devkit/build-webpack     0.803.29
@angular-devkit/core              8.3.29
@angular-devkit/schematics        8.3.29
@angular/cli                      8.3.29
@ngtools/webpack                  8.3.29
@schematics/angular               8.3.29
@schematics/update                0.803.29
rxjs                              6.4.0
typescript                        3.5.3
webpack                           4.39.2

Akitaインストール

まず、Angular CLIを使ってAngularプロジェクト(akita-counter)を作成します。

$ ng new akita-counter

続いて、Akitaライブラリをインストールします。

$ ng add @datorama/akita

Akita CLIもグローバルインストールします。

$ npm install @datorama/akita-cli -g

カウンターアプリ作成

値を一つずつ増やしたり減らしたりできるカウンターアプリを作成していきます。

Store

まず、カウンターの値を保持するStoreを作成します。

Akitaには通常のStoreとデータベースのテーブルのような取り扱いができるEntityStoreがありますが、今回の例では通常のStoreを使用しています。

counter.store.ts

import { Injectable } from '@angular/core';
import { Store, StoreConfig } from '@datorama/akita';

export interface CounterState {
  counter: number;
}

export function createInitialState(): CounterState {
  return {
    counter: 0,
  };
}
@Injectable({ providedIn: 'root' })
@StoreConfig({ name: 'counter' })
export class CounterStore extends Store<CounterState> {
  constructor() {
    super(createInitialState());
  }
}

Query

続いて、Storeから現在のカウンターの値を取得するQueryを作成します。

counter.query.ts

import { Injectable } from '@angular/core';
import { Query } from '@datorama/akita';
import { CounterState, CounterStore } from './counter.store';
@Injectable({ providedIn: 'root' })
export class CounterQuery extends Query<CounterState> {
  constructor(protected store: CounterStore) {
    super(store);
  }
}

Service

カウンターの値を更新する機能を持つServiceを作成します。加算を行うincrement()と減算を行うdecrement()を作成しました。

counter.service.ts

import { Injectable } from '@angular/core';
import { CounterStore } from './counter.store';
@Injectable({ providedIn: 'root' })
export class CounterService {
  constructor(private counterStore: CounterStore) {}

  increment() {
    this.counterStore.update((state) => ({
      counter: state.counter + 1,
    }));
  }

  decrement() {
    this.counterStore.update((state) => ({
      counter: state.counter - 1,
    }));
  }
}

increment()decrement()の内部ではStoreの値を更新するためにStoreのメソッドであるupdate()を呼び出しています。

Component

カウンター画面を作成します。QueryとServiceをDIすることで、カウントの取得と更新を行っています。

counter.component.ts

import { Component, OnInit } from '@angular/core';
import { CounterService } from './state/counter.service';
import { CounterQuery } from './state/counter.query';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-counter',
  templateUrl: './counter.component.html',
  styleUrls: ['./counter.component.scss'],
})
export class CounterComponent implements OnInit {
  readonly counter$: Observable<number>;

  constructor(
    private counterService: CounterService,
    private counterQuery: CounterQuery
  ) {
    this.counter$ = this.counterQuery.select('counter');
  }

  ngOnInit() {}

  increment() {
    this.counterService.increment();
  }

  decrement() {
    this.counterService.decrement();
  }
}

Storeの値を取得するメソッドとして、Queryは2種類のメソッドを用意しています。select()getValue()です。select()ではStoreの値をObservable型で返します。getValue()はStoreの生の値を返します。

今回の例では、Storeの値の取得にselect()を使っています。

counter.component.html

<button (click)="increment()">Increment</button>
<button (click)="decrement()">Decrement</button>
{{ counter$ | async }}

上記のコードを実行すると、このようなカウンターアプリが表示されます。

f:id:l08084:20201006222153p:plain
カウンターアプリ

なお、AkitaはChromeのRedux Devtools Extensionで値の変遷を追うことができます。

f:id:l08084:20201007152716p:plain
AkitaにRedux Devtools Extentionを使っているところ

次回の記事

【Angular】Akita学習(2) - TODOアプリ作成 - 中安拓也のブログ

参考サイト

Akita | Reactive State Management

https://engineering.datorama.com/@NetanelBasal

Angular向け状態管理ライブラリAkitaの紹介 - Qiita

Angularのシンプルな状態管理ライブラリ Akita について - Qiita

Akita🐶でがんばる状態管理 - Qiita

素朴な Angular 向けの type safe で immutable な Flux Store - Qiita

Angular + Firebase でGoogle認証

f:id:l08084:20201004191022p:plain

はじめに

Ionic(Angular)のアプリでFirebase認証によるGoogle認証を実装します。

関連記事

メールアドレス/パスワード、Twitter、FacebookによるFirebase認証については過去に記事にしています。

  • メールアドレス/パスワードによる認証

AngularでFirebase認証(その1) Firebaseのセットアップ - 中安拓也のブログ

AngularでFirebase認証(その2) Angular Materialを使ったログイン画面の作成 - 中安拓也のブログ

AngularでFirebase認証(その3) Firebase Authentication の呼び出し - 中安拓也のブログ

Angular + Firebase でアカウント登録画面の作成 - 中安拓也のブログ

  • Twitter認証

Angular + Firebase でTwitter認証 - 中安拓也のブログ

  • Facebook認証

Angular + Firebase でFacebook認証 - 中安拓也のブログ

  • Firebase認証のリダイレクトモード

【Angular】リダイレクトモードでFirebase認証を行う - 中安拓也のブログ

環境

ハイブリットモバイルアプリ用フレームワークであるIonic(Angular)とFirebaseを使用してアプリを作成しています。

  • firebase@7.21.1

$ ionic infoコマンドの実行結果

$ ionic info

Ionic:

   Ionic CLI                     : 6.11.8 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.3.3
   @angular-devkit/build-angular : 0.1000.8
   @angular-devkit/schematics    : 10.0.8
   @angular/cli                  : 10.0.8
   @ionic/angular-toolkit        : 2.3.3

Capacitor:

   Capacitor CLI   : 2.4.1
   @capacitor/core : 2.4.1

Utility:

   cordova-res : not installed
   native-run  : not installed

System:

   NodeJS : v12.13.1 (/usr/local/bin/node)
   npm    : 5.6.0
   OS     : macOS Catalina

FirebaseコンソールでGoogle認証を有効にする

f:id:l08084:20201005221739p:plain
ログイン方法タブでGoogleを有効にする

  1. Firebaseコンソールで[Authentication]セクションを開きます。
  2. [ログイン方法]タブで[Google]を有効にし、[保存]をクリックします。

Googleのログインボタン作成

Step1: Googleのロゴアイコンを取得

まず、Icons8からGoogleのロゴアイコンをダウンロードします。

f:id:l08084:20201005215901p:plain
Googleのロゴ

Step2: GoogleカラーをIonicに追加

GoogleのログインボタンのカラーをIonicの配色に追加します。配色の追加方法については、下記の記事を参考にしてください。

【Ionic v5】Colorsに色を追加してボタンに適用する - 中安拓也のブログ

Step3: ログインボタンのテンプレートを作成

login.page.html

      <ion-button (click)="signInWithGoogle()" color="google" class="login-button google">
        <img class="g-icon" src="assets/icon/google-logo.png">Googleでログイン
      </ion-button>

login.page.scss

.login-button {
    font-weight: bold;
    margin-bottom: 18px;
    width: 100%;

    &.google {
        --border-style: solid;
        --border-width: 1px;
    }

    .g-icon {
        position: absolute;
        left: 15px;
        top: 25%;
        width: 1.4em;
    }
}

f:id:l08084:20201006125139p:plain
作成されたGoogleのログインボタン

Step4: リダイレクトモードでFirebase認証(Google)

モバイルデバイスによる認証を想定しているため、FirebaseのGoogle認証をリダイレクトモード(signInWithRedirect)で実装していきます。

下記のコードはFirebase認証を呼び出すサービスクラスです。ログイン画面から呼び出されます。

authentication.service.ts

export class AuthenticationService {
  constructor(public afAuth: AngularFireAuth) {}

  /**
   * Google認証を呼び出す。
   * 認証成功時にリダイレクトする。
   *
   * @returns {Promise<void>}
   * @memberof AuthenticationService
   */
  public signInWithGoogle(): Promise<void> {
    return this.afAuth.signInWithRedirect(
      new firebase.auth.GoogleAuthProvider()
    );
  }

  /**
   * リダイレクト後の処理。
   *
   * @returns {Promise<firebase.auth.UserCredential>}
   * @memberof AuthenticationService
   */
  public getRedirectResult(): Promise<firebase.auth.UserCredential> {
    return this.afAuth.getRedirectResult();
  }
}

下記のコードはログイン画面のコンポーネントクラスです。

login.page.ts

export class LoginPage implements OnInit {
  constructor(
    private authenticationService: AuthenticationService,
    private router: Router
  ) {}

  ngOnInit() {
    this.getRedirectResult();
  }

  /**
   * Googleで認証する。
   *
   * @memberof LoginPage
   */
  public async signInWithGoogle() {
    await this.authenticationService.signInWithGoogle();
  }

  /**
   * リダイレクト後に呼び出される処理。
   *
   * @private
   * @memberof LoginPage
   */
  private async getRedirectResult() {
    const result: firebase.auth.UserCredential = await this.authenticationService.getRedirectResult();
    try {
      if (result.user != null) {
        this.router.navigate(['/weight/tabs/tab1']);
      }
    } catch (error) {
      console.log(error);
    }
  }
}

上記のコードで行っているのは、下記の内容です。

  1. ユーザーがsignInWithGoogle()でログインします。
  2. Google でログインを行うため、signInWithRedirect()メソッドによってリダイレクトがトリガーされます。
  3. Googleログインをすると、ユーザーはログイン画面のコンポーネントに戻されます。
  4. ユーザーのログインは、ログイン画面のngOnInit()内のgetRedirectResult()で返される Promise によって解決されます。
  5. navigate()メソッドで、ルーターがユーザーを/weight/tabs/tab1に移動させます。

参考サイト

ログインにおけるブランドの取り扱いガイドライン  |  Google Identity Platform  |  Google Developers

【Ionic v5】[Shadow DOM]ボタンのレイアウトを変更する

f:id:l08084:20201004190825p:plain

はじめに

本記事では、Ionic v5のボタンのレイアウトを変更します。Ionic v5のボタン(ion-button)ではShadow DOMが適用されているため、Shadow DOMでないコンポーネントと同様のやり方でレイアウトを変更することができません。

ボタンのレイアウト変更を通して、Ionic v5のShadow DOMが適用されているコンポーネントのレイアウトの変更方法について説明します。

Shadow DOMって?

Shadow DOMとはDOMに対してカプセル化を提供する仕組みになります。

Shadow DOMによってカプセル化されたDOMは、特定の方法でしかその中身にアクセスできなくなります。

Ionic v5の例で言えば、Shadow DOM化されているボタン(ion-button)のレイアウトを変更するために、あらかじめ用意されたCSS変数経由でレイアウトを変更する必要があります。

Ionic v5では下記のコンポーネントがShadow DOM化されています。

  • ボタン
  • Card
  • Segment
  • Split Window

ボタンのレイアウトを変更する

続いて、Ionicのボタンのレイアウトを変更する具体的な方法について説明します。

今回は、例として下記のボタンに枠線を付けるレイアウト変更を実施します。

f:id:l08084:20201004193153p:plain
枠線がないボタンに枠線をつける

CSS変数の--border-style--border-widthを経由してボタンに枠線をつけていきます。

login.page.html

      <ion-button color="google" class="login-button google">
        <img class="g-icon" src="assets/icon/google-logo.png">Googleでログイン
      </ion-button>

login.page.scss

.login-button {
    font-weight: bold;
    margin-bottom: 18px;
    width: 100%;

    &.google {
        --border-style: solid;
        --border-width: 1px;
    }

    .g-icon {
        position: absolute;
        left: 15px;
        top: 25%;
        width: 1.4em;
    }
}

f:id:l08084:20201004200126p:plain
ボタンに枠線がついた

参考サイト

先取り、Shadow DOM - Shadow DOMが生まれた理由 | CodeGrid

(中級者向け)Ionic 4でのCSSカスタム変数の使い方 - Qiita

[翻訳]Ionic5がやってきた。Gifアニメーションつきで最新のWebモバイルUIフレームワークを紹介!|榊原昌彦|note

CSS変数 - Ionic Framework 日本語ドキュメンテーション

https://ionicframework.com/docs/api/button

【Angular】リダイレクトモードでFirebase認証を行う

f:id:l08084:20200930195042p:plain

はじめに

FirebaseのSNS認証には、ポップアップウィンドウを表示するポップアップモードとログインページにリダイレクトするリダイレクトモードの2種類の形式があります。

今回は、後者のリダイレクトモードの実装方法について説明します。

環境

ハイブリットモバイルアプリ用フレームワークであるIonic(Angular)とFirebaseを使用してアプリを作成しています。

  • firebase@7.21.1

$ ionic infoコマンドの実行結果

$ ionic info

Ionic:

   Ionic CLI                     : 6.11.8 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.3.3
   @angular-devkit/build-angular : 0.1000.8
   @angular-devkit/schematics    : 10.0.8
   @angular/cli                  : 10.0.8
   @ionic/angular-toolkit        : 2.3.3

Capacitor:

   Capacitor CLI   : 2.4.1
   @capacitor/core : 2.4.1

Utility:

   cordova-res : not installed
   native-run  : not installed

System:

   NodeJS : v12.13.1 (/usr/local/bin/node)
   npm    : 5.6.0
   OS     : macOS Catalina

リダイレクトモードでFirebase認証

FirebaseのTwitter認証をリダイレクトモードで実装していきます。なお、モバイルデバイスの認証ではポップアップモードではなくリダイレクトモードが推奨されています。

リダイレクトモードでは、signInWithRedirectを呼び出します。Twitter認証後(リダイレクト後)には、getRedirectResultが呼び出されます。

下記のコードはFirebase認証を呼び出すサービスクラスです。ログイン画面から呼び出されます。

authentication.service.ts

export class AuthenticationService {
  constructor(public afAuth: AngularFireAuth) {}

  /**
   * Twitter認証を呼び出す。
   * 認証成功時にリダイレクトする。
   *
   * @returns {Promise<void>}
   * @memberof AuthenticationService
   */
  public signInWithTwitter(): Promise<void> {
    return this.afAuth.signInWithRedirect(
      new firebase.auth.TwitterAuthProvider()
    );
  }

  /**
   * リダイレクト後の処理。
   *
   * @returns {Promise<firebase.auth.UserCredential>}
   * @memberof AuthenticationService
   */
  public getRedirectResult(): Promise<firebase.auth.UserCredential> {
    return this.afAuth.getRedirectResult();
  }
}

下記のコードはログイン画面のコンポーネントクラスです。

login.page.ts

export class LoginPage implements OnInit {
  constructor(
    private authenticationService: AuthenticationService,
    private router: Router
  ) {}

  ngOnInit() {
    this.getRedirectResult();
  }

  /**
   * Twitterで認証する。
   *
   * @memberof LoginPage
   */
  public async signInWithTwitter() {
    await this.authenticationService.signInWithTwitter();
  }

  /**
   * リダイレクト後に呼び出される処理。
   *
   * @private
   * @memberof LoginPage
   */
  private async getRedirectResult() {
    const result: firebase.auth.UserCredential = await this.authenticationService.getRedirectResult();
    try {
      if (result.user != null) {
        this.router.navigate(['/weight/tabs/tab1']);
      }
    } catch (error) {
      console.log(error);
    }
  }
}

上記のコードで行っているのは、下記の内容です。

  1. ユーザーがsignInWithTwitter()でログインします。
  2. Twitter でログインを行うため、signInWithRedirect()メソッドによってリダイレクトがトリガーされます。
  3. Twitterログインをすると、ユーザーはログイン画面のコンポーネントに戻されます。
  4. ユーザーのログインは、ログイン画面のngOnInit()内のgetRedirectResult()で返される Promise によって解決されます。
  5. navigate()メソッドで、ルーターがユーザーを/weight/tabs/tab1に移動させます。

ポップアップモードのFirebase認証

ポップアップモードによるFirebaseのTwitter認証は下記の記事を参照してください。

Angular + Firebase でTwitter認証 - 中安拓也のブログ

参考サイト

JavaScript で Google ログインを使用して認証する  |  Firebase

Google Developers Japan: Angular コンポーネントから Route 特有のコードを綺麗にする

【Ionic v5】Colorsに色を追加してボタンに適用する

f:id:l08084:20200927151329p:plain

はじめに

f:id:l08084:20200927151907p:plain
Ionicには9つのデフォルトカラーがある

Ionicにはprimary, secondary...といったように9つのデフォルトカラーがあり、color属性を使用することで任意の色をIonicのコンポーネントに適用することができます。

今回の記事では、デフォルトカラー以外の色を使用するために、色をcolorに追加する方法を説明します。

環境

$ ionic infoコマンドの実行結果

$ ionic info

Ionic:

   Ionic CLI                     : 6.11.8 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.3.3
   @angular-devkit/build-angular : 0.1000.8
   @angular-devkit/schematics    : 10.0.8
   @angular/cli                  : 10.0.8
   @ionic/angular-toolkit        : 2.3.3

Capacitor:

   Capacitor CLI   : 2.4.1
   @capacitor/core : 2.4.1

Utility:

   cordova-res : not installed
   native-run  : not installed

System:

   NodeJS : v12.13.1 (/usr/local/bin/node)
   npm    : 5.6.0
   OS     : macOS Catalina

配色の追加

今回は、Twitterのログインボタンを作るために、Twitterカラー(#00acee)をIonicの配色に追加します。

Step1: レイヤードスタイルの作成

まず、New Color Creatorを使用して、追加したい色のレイヤードスタイルを作成します。

f:id:l08084:20200927170241p:plain
New Color Creator

新色Twitter色を作成しました。

New Color Creatorで作成したレイヤードスタイル

:root {
  --ion-color-twitter: #00acee;
  --ion-color-twitter-rgb: 0,172,238;
  --ion-color-twitter-contrast: #000000;
  --ion-color-twitter-contrast-rgb: 0,0,0;
  --ion-color-twitter-shade: #0097d1;
  --ion-color-twitter-tint: #1ab4f0;
}

.ion-color-twitter {
  --ion-color-base: var(--ion-color-twitter);
  --ion-color-base-rgb: var(--ion-color-twitter-rgb);
  --ion-color-contrast: var(--ion-color-twitter-contrast);
  --ion-color-contrast-rgb: var(--ion-color-twitter-contrast-rgb);
  --ion-color-shade: var(--ion-color-twitter-shade);
  --ion-color-tint: var(--ion-color-twitter-tint);
}

ボタンのラベルとアイコンを黒ではなく白にしたいので、--ion-color-twitter-contrast--ion-color-twitter-contrast-rgbを黒から白にします。

New Color Creatorで作成したレイヤードスタイル(修正版)

:root {
  --ion-color-twitter: #00acee;
  --ion-color-twitter-rgb: 0, 172, 238;
  --ion-color-twitter-contrast: #ffffff;
  --ion-color-twitter-contrast-rgb: 255, 255, 255;
  --ion-color-twitter-shade: #0097d1;
  --ion-color-twitter-tint: #1ab4f0;
}

.ion-color-twitter {
  --ion-color-base: var(--ion-color-twitter);
  --ion-color-base-rgb: var(--ion-color-twitter-rgb);
  --ion-color-contrast: var(--ion-color-twitter-contrast);
  --ion-color-contrast-rgb: var(--ion-color-twitter-contrast-rgb);
  --ion-color-shade: var(--ion-color-twitter-shade);
  --ion-color-tint: var(--ion-color-twitter-tint);
}

Step2: SCSSファイルにレイヤードスタイルを追加

続いて、作成したレイヤードスタイルをSCSSファイルに転記していきます。

まず、variables.scssファイルの:root内部にレイヤードスタイルの上部を転記します。

src\theme\variables.scss

  --ion-color-twitter: #00acee;
  --ion-color-twitter-rgb: 0, 172, 238;
  --ion-color-twitter-contrast: #ffffff;
  --ion-color-twitter-contrast-rgb: 255, 255, 255;
  --ion-color-twitter-shade: #0097d1;
  --ion-color-twitter-tint: #1ab4f0;

次に、global.scssにレイヤードスタイルの下部を転記します。

src/global.scss

.ion-color-twitter {
    --ion-color-base: var(--ion-color-twitter);
    --ion-color-base-rgb: var(--ion-color-twitter-rgb);
    --ion-color-contrast: var(--ion-color-twitter-contrast);
    --ion-color-contrast-rgb: var(--ion-color-twitter-contrast-rgb);
    --ion-color-shade: var(--ion-color-twitter-shade);
    --ion-color-tint: var(--ion-color-twitter-tint);
}

Step3: ボタン作成

Step1とStep2で配色の追加は完了したので、追加した色を使用して、Twitterのログインボタンを作成します。

      <ion-button color="twitter" class="login-button">
        <ion-icon class="sns-icon" name="logo-twitter"></ion-icon>Twitterでログイン
      </ion-button>

color="twitter"という風に指定してあげることで、Step1, 2で作成したTwitterカラーがボタンに反映されます。

f:id:l08084:20200927195348p:plain
作成したTwitterボタン

参考サイト

配色 - Ionic Framework 日本語ドキュメンテーション

Ionic 4: How to add more colors and use them as color in buttons and more? | by Paul Stelzer | Medium

【Ionic v5】テーブル内でラジオボタンを使用する

f:id:l08084:20200921011913p:plain
テーブル内でラジオボタンを使用する

はじめに

テーブルのセル内でIonicのラジオボタンを使用する方法がわからず、手間取ってしまったため、メモとして残した。

環境

$ ionic infoコマンドの実行結果

$ ionic info

Ionic:

   Ionic CLI                     : 6.11.8 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.3.3
   @angular-devkit/build-angular : 0.1000.8
   @angular-devkit/schematics    : 10.0.8
   @angular/cli                  : 10.0.8
   @ionic/angular-toolkit        : 2.3.3

Capacitor:

   Capacitor CLI   : 2.4.1
   @capacitor/core : 2.4.1

Utility:

   cordova-res : not installed
   native-run  : not installed

System:

   NodeJS : v12.13.1 (/usr/local/bin/node)
   npm    : 5.6.0
   OS     : macOS Catalina

通常のIonicのラジオボタン

f:id:l08084:20200921021014p:plain
普通のIonicのラジオボタン

普通にIonicのラジオボタンを使用する場合は、次のようにコードを書く。

  <!-- 通常のラジオボタン -->
  <ion-list>
    <ion-radio-group [(ngModel)]="selectedAnimal">
      <ion-item>
        <ion-label>Dog</ion-label>
        <ion-radio value="dog"></ion-radio>
      </ion-item>
      <ion-item>
        <ion-label>Cat</ion-label>
        <ion-radio value="cat"></ion-radio>
      </ion-item>
      <ion-item>
        <ion-label>Fish</ion-label>
        <ion-radio value="fish"></ion-radio>
      </ion-item>
    </ion-radio-group>
  </ion-list>

1組のion-list, ion-radio-group内に、ラジオボタンの数だけ、ion-radioを定義するとラジオボタンが作成される

テーブルのセル内でion-radioを使用する

f:id:l08084:20200921021614p:plain
テーブルのセル内にラジオボタンを作成した場合

テーブルのセル内にラジオボタンを定義したい場合は、次のようにコードを書く。

  <div class="radio-button-table">
    <!-- テーブル -->
    <table>
      <thead>
        <tr>
          <th>
            Apple
          </th>
          <th>
            Grape
          </th>
          <th>
            Cherry
          </th>
        </tr>
      </thead>
      <tbody>
        <!-- ラジオボタン -->
        <tr>
          <th>
            <ion-list>
              <ion-radio-group [(ngModel)]="selectedFruit">
                <ion-item>
                  <ion-radio value="apple"></ion-radio>
                </ion-item>
              </ion-radio-group>
            </ion-list>
          </th>
          <th>
            <ion-list>
              <ion-radio-group [(ngModel)]="selectedFruit">
                <ion-item>
                  <ion-radio value="grape"></ion-radio>
                </ion-item>
              </ion-radio-group>
            </ion-list>
          </th>
          <th>
            <ion-list>
              <ion-radio-group [(ngModel)]="selectedFruit">
                <ion-item>
                  <ion-radio value="cherry"></ion-radio>
                </ion-item>
              </ion-radio-group>
            </ion-list>
          </th>
        </tr>
      </tbody>
    </table>
  </div>
  <!-- 選択したラジオボタンの値が表示される -->
  <div>{{selectedFruit}}</div>

普通にラジオボタンを使用する場合と違って、ラジオボタン一つにつき、ion-list, ion-radio-groupもそれぞれ書いてあげる必要がある。

参考サイト

ion-radio - Ionic Documentation

【競プロ】[C++/TypeScript]B - Lucas Numberが解けない

はじめに

競技プログラミングする前にC++の勉強しよ〜〜と思ってC++入門 AtCoder Programming Guide for beginners (APG4b)をやっていたんですが、その中で出題された「AtCoder Beginner Contest 079」の「B - Lucas Number」が解けない。。。。何度やっても実行時間制限超過(TLE)になり、不正解になってしまう。

「B - Lucas Number」 とは

1 ~ 86番目のリュカ数を計算するソースコードを書きなさい。という問題になります。 問題の詳細については、下記リンクを参照

atcoder.jp

間違った回答

TypeScriptで回答して、実行時間制限超過(TLE)になったあとTypeScriptよりも早い言語(C++)なら大丈夫かな?と思ってC++でも解いてみたんですが、どのみち実行時間制限超過(TLE)になりました。

TypeScriptでの回答

import * as fs from "fs";

const input = fs.readFileSync("/dev/stdin", "utf8");
const a = +input;

console.log(lucasNumber(a));

function lucasNumber(n: number): number {
  if (n === 0) {
    return 2;
  } else if (n === 1) {
    return 1;
  } else {
    return lucasNumber(n - 1) + lucasNumber(n - 2);
  }
}

C++での回答

#include <iostream>
using namespace std;

int64_t lucas_number(int64_t n)
{
    if (n == 0)
    {
        return 2;
    }
    else if (n == 1)
    {
        return 1;
    }
    else
    {
        return lucas_number(n - 1) + lucas_number(n - 2);
    }
}

int main()
{
    // 整数の入力
    int64_t n;
    cin >> n;
    cout << lucas_number(n) << endl;
    return 0;
}

問題文を読んで、はは〜ん再帰だな?と思って再帰を使って解いてみたんですが、実行時間制限超過(TLE)で不正解に....

f:id:l08084:20200911215717p:plain
2200msかかってTLEで不正解に

C++での正しい回答

再帰を使うのをやめて、配列を使うと、実行時間制限内で計算が完了して、正解になりました。

C++での回答

#include <bits/stdc++.h>
using namespace std;

int main()
{
    // 整数の入力
    int N;
    cin >> N;
    vector<int64_t> L(N + 1);

    if (N == 1)
    {
        L.at(N) = 1;
    }
    else
    {
        L.at(0) = 2;
        L.at(1) = 1;
        for (int i = 2; i <= N; i++)
        {
            L.at(i) = L.at(i - 1) + L.at(i - 2);
        }
    }
    cout << L.at(N) << endl;
    return 0;
}

86番目のリュカ数は、939587134549734843になるため、int型で表せる2147483647 を超えてしまいます。そのため、int型ではなくint64_t型を使用する必要があります。

f:id:l08084:20200913183424p:plain
再帰をやめると実行時間が1/200くらいになって正解になった

TypeScriptでの正しい回答

TypeScriptでも再帰をやめると、実行時間制限以内に計算が終わるようになります。

import * as fs from "fs";

const input = fs.readFileSync("/dev/stdin", "utf8");
const N = +input;

const L: BigInt[] = Array(N + 1);

if (N === 1) {
  L[N] = BigInt(1);
} else {
  L[0] = BigInt(2);
  L[1] = BigInt(1);

  for (let i = 2; i <= N; i++) {
    L[i] = BigInt(L[i - 1]) + BigInt(L[i - 2]);
  }
}

console.log(String(L[N]));

TypeScript(JavaScript)のnumber型で表現できる最大値は1.8×10^308であるため、正確な値を計算するためには、BigIntを使用する必要があります。

参考サイト

JavaScript/TypeScriptで競技プログラミングをするには 後編 | わたしろぐ

https://mrsekut.site/?p=3354

BigInt - JavaScript | MDN