Angularでpipeを使って日付データをフォーマットする

1

準備

標準機能なので追加のインストールは必要ありません.module.tsで@angular/commonからDatePipeをインストールし,providersに登録します.

import { IonicModule } from '@ionic/angular';
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Tab3Page } from './tab3.page';
import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
+import { DatePipe } from '@angular/common';

import { Tab3PageRoutingModule } from './tab3-routing.module';

@NgModule({
  imports: [
    IonicModule,
    CommonModule,
    FormsModule,
    ExploreContainerComponentModule,
    RouterModule.forChild([{ path: '', component: Tab3Page }]),
    Tab3PageRoutingModule,
  ],
-  declarations: [Tab3Page]
+  declarations: [Tab3Page],
+  providers: [DatePipe]
})
export class Tab3PageModule {}

次に,page.tsファイルで読み込みます.

import { Component,ElementRef,ViewChild } from '@angular/core';
+import { DatePipe } from '@angular/common';

@Component({
  selector: 'app-tab3',
  templateUrl: 'tab3.page.html',
  styleUrls: ['tab3.page.scss']
})
export class Tab3Page {

  constructor(
+    private datePipe: DatePipe
  ) {
  }

  ionViewDidEnter(){
  }

}

HTMLでの表示を変えたい場合

import { Component,ElementRef,ViewChild } from '@angular/core';
import { DatePipe } from '@angular/common';

@Component({
  selector: 'app-tab3',
  templateUrl: 'tab3.page.html',
  styleUrls: ['tab3.page.scss']
})
export class Tab3Page {
+  time_on_html:any;
  constructor(
    private datePipe: DatePipe
  ) {
  }

  ionViewDidEnter(){
+    this.time_on_html = new Date();
+    console.log(this.time_on_html);
  }

}

そして,HTML側は{{time_on_html | date:”MM/dd/yy” }}で参照します.

tsファイルの中で表示を変えたい場合

import { Component,ElementRef,ViewChild } from '@angular/core';
import { DatePipe } from '@angular/common';

@Component({
  selector: 'app-tab3',
  templateUrl: 'tab3.page.html',
  styleUrls: ['tab3.page.scss']
})
export class Tab3Page {
  time_on_html:any;
  constructor(
    private datePipe: DatePipe
  ) {
  }

  ionViewDidEnter(){
    this.time_on_html = this.datePipe.transform(new Date(), 'yyyyMMddHHmmss');
    console.log(this.time_on_html);

  }

}

2021/07/19 18:42:52と表示される.

Angular(Ionic)で画像にお絵描き

1

Angular(Ionic)で画像にお絵描きする最も簡単な方法はプラグインを使うことです.

https://www.npmjs.com/package/ngx-image-drawing

インストール

npm install --save ngx-image-drawing

使い方

モジュールに追加

import { ImageDrawingModule } from 'ngx-image-drawing';

@NgModule({
  imports: [
    ImageDrawingModule
  ],
  declarations: []
})
export class Tab1PageModule {}

あとはHMTLから呼び出せる

<image-drawing
  <div class="upload">
      <input type="file" accept="image/*"
             (change)="fileChangeEvent($event)">
      <img [src]="imgSrc" alt="">
  </div>

  <image-drawing
      [src]="imageUrl"
      outputMimeType="'image/jpeg'"
      outputQuality="0.8"
      (save)="save($event)"
      (cancel)="cancel()">
  </image-drawing>

tsファイルはこれ


    fileChangeEvent(event: any): void {

    //fileが選択されていなければリセット
    if (event.target.files.length === 0) {
      this.file = null;
      this.imgSrc = "";
      return;
    }

    //ファイルの情報をfileとimgSrcに保存
    let reader = new FileReader();
    this.file = event.target.files[0];
    reader.onload = () => {
      this.imgSrc = reader.result;
      this.imageUrl = reader.result;
      //this.cx.drawImage(event.target.files[0], 0, 0, this.width, this.height);

      console.log(this.imgSrc);

    }
    reader.readAsDataURL(this.file);

  }


  save(event: any){
    console.log(event);
    const url = window.URL.createObjectURL(event);
    let title = "Angular_sample_file";
    //this.cx.drawImage(event, 0, 0, this.width, this.height);

    // aタグを作成して無理やりクリック -> ダウンロード機能発火
    let a = document.createElement('a');
    document.body.appendChild(a);
    a.setAttribute('style', 'display: none');
    a.href = url;
    a.download = title;
    a.click();
    window.URL.revokeObjectURL(url);
  }

プラグインを使わない方法

https://medium.com/@tarik.nzl/creating-a-canvas-component-with-free-hand-drawing-with-rxjs-and-angular-61279f577415

Ionicでカスタムパイプを作成する

1

あるページ内で利用するカスタムパイプを作成する.ngForで繰り返した要素に対して文字列をサニタイズする方法について.一つの要素だけであれば下記方法に従えばできるが,ngForで繰り返した要素に対してサニタイズする方法について.

ionic g pipe app-summarization/customPipe1

そうすると,そのページのモジュールページに下記のコードが追加される.もしされない場合は手動で追加する.

import { CustomPipe1Pipe } from ‘./custom-pipe1.pipe’;

@NgModule({
imports: [
**],
declarations: [**,CustomPipe1Pipe]
})

今回はcustom-pipe1.pipe.tsでサニタイズするので

import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

@Pipe({
  name: 'sanitizeHTML'
})
export class CustomPipe1Pipe implements PipeTransform {
  constructor(private sanitizer: DomSanitizer) {}
  transform(code) {
    return this.sanitizer.bypassSecurityTrustHtml(code);
  }
}

使う場所はこんな感じ

    <div *ngFor="let message of messages;">
      <div [ngSwitch]="message['isAi']">
         <div *ngSwitchCase="'true'">
           <div class="media" >
             <div class="media-left">
               <a href="#" class="icon-rounded">AI</a>
             </div>
             <div class="media-body">
               <h4 class="media-heading">Sunny Date:2021/04/17</h4>
               <div [innerHTML]="message.message">{{message.message| sanitizeHTML}}</div>
             </div>
           </div>
         </div>
      </div>
    </div>

Ionicで特定の要素まで自動でスクロールする

1

tsファイルで

import {ViewChild} from ‘@angular/core’
import { IonContent } from ‘@ionic/angulaIonicで特定の要素まで自動でスクロールするr’;

を読み込んだあと,

@ViewChild(IonContent, { static: false }) content: IonContent;

でIoncontentを参照する(@ViewChild(‘ion-content’) content: IonContent;とかくと動かない)

あとは

ionViewDidEnter(){
  this.content.scrollToPoint(0, 600, 300)
}

という感じで指定する.x,y,秒.

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import {ViewChild} from '@angular/core'
import { IonContent } from '@ionic/angular';

@Component({
  selector: 'app-tab3',
  templateUrl: './tab3.page.html',
  styleUrls: ['./tab3.page.scss'],
})
export class Tab3Page implements OnInit {
  //@ViewChild('ion-content') content: IonContent;
  @ViewChild(IonContent, { static: false }) content: IonContent;


    ionViewDidEnter(){
      this.content.scrollToPoint(0, 600, 300)
    }

Angular(Ionic)でHTTPリクエスト

1

AngularでバックエンドAPIへアクセスしてデータを取得する際、認証情報やクエリパラメータを付与したい時がある。

Post通信

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({ selector: 'app', templateUrl: 'app.component.html' })
export class AppComponent implements OnInit {
    postId;

    constructor(private http: HttpClient) { }

    ngOnInit() {      
    const headers = { 'Authorization': 'Bearer my-token', 'My-Custom-Header': 'foobar' };
    const body = { title: 'Ionic POST Request Example' };
    this.http.post('https://example.com', body, { headers }).subscribe(data => {
        console.log(data);
    });
    }
}

Get通信

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({ selector: 'app', templateUrl: 'app.component.html' })
export class AppComponent implements OnInit {
    postId;

    constructor(private http: HttpClient) { }

    ngOnInit() {      
    const headers = { 'Authorization': 'Bearer my-token', 'My-Custom-Header': 'foobar' };
    const parameters = { title: 'Ionic POST Request Example' };
    this.http.post('https://example.com',  { headers, parameters }).subscribe(data => {
        console.log(data);
    });
    }
}

Integrate adsense in Ionic Angular.

1

Note:please keep in mind that I am not native English speaker.

In this article, I would like to summarize how to insert ads into Ionic (Angular) project using adsense.

Admob is more popular than adsense for Ionic, but Admob does not support web platforms. If you want to deploy your project as a web app, you need to use Adsense instead of Admob.

However, there is little information on how to use Adsense with Angular, let alone Ionic. Therefore, I would like to introduce the steps here in this article.

Summary

Basically, I recommend you to use ng2-adsense plugin, but it doesn’t work even if you implement it according to the instructions on the official page. You need to add some other code. In addition, it is important to have a ad with fixed size(not responsive). If you set your ad style with responsive, it will not work. Lastly, I recommend you to place your ad on the top area for your page. It may sounds nonsense, but it was important to me.

GitHub page

https://github.com/NP-Systems/Ionic-adsense-demo

Demo site

https://adsense-demo.np-sys.com

Environment

Angular11, Ionic6

Ionic:

   Ionic CLI                     : 6.13.1 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.6.0
   @angular-devkit/build-angular : 0.1102.4
   @angular-devkit/schematics    : 9.1.6
   @angular/cli                  : 11.2.4
   @ionic/angular-toolkit        : 2.2.0

Capacitor:

   Capacitor CLI   : 2.1.2
   @capacitor/core : 2.1.2

Utility:

   cordova-res                          : not installed
   native-run (update available: 1.3.0) : 1.0.0

System:

   NodeJS : v12.18.0 (/usr/local/bin/node)
   npm    : 6.14.6
   OS     : macOS Big Sur

the version of ng2-adsense was 9.1.0.

Steps

Create your project

ionic start adsense-demo blank --type=angular
cd  adsense-demo

Install plugin from here . https://github.com/scttcper/ng2-adsense

npm install ng2-adsense

Please note that you need to install the one that matches your version of Angular. You can specify plugin version as followings.

npm install ng2-adsense@9.1.0

Next, add the following snippet in Index.html(above </head>).

<script async src=//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js></script>

It should look like the picture below.

Next, add following codes to the module where you want to place your ad. Officially, it says to add to app.module.ts, but please add it into the module you want to use. For example, add code into home.module.ts as shown below.

The codes you need to add are followings.

import { AdsenseModule } from 'ng2-adsense';
@NgModule({
    imports: [
    <Other modules>,
      AdsenseModule.forRoot({
        adClient: 'ca-pub-XXXXXXXXXXXXXX',
        adSlot: XXXXXXXXXXX,
      }),

You don’t have to add anything to home.page.ts.

Then add snippet below into home.page.html.

  </ion-header>
<ion-content>

<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
  <!-- ocr-app -->
  <ins class="adsbygoogle"
       style="display:inline-block;width:728px;height:90px"
       data-ad-client="ca-pub-XXXXXXXXXXXXXX"
       data-ad-slot="XXXXXXXXX"></ins>
  <script>
       (adsbygoogle = window.adsbygoogle || []).push({});
  </script>

  <ng-adsense></ng-adsense>

Please note the followings.

  • Create ad styles in fixed size, not responsive mode (I recommend you to create ad with inline-block; width: 728px; height: 90px).
  • Add the snippet on top of the ion-content tag.

If you set ad style to responsive, it will not work. If you do not add it to the top of the ion-content tag, it will not work neither. I know it sounds strange, but I think Adsense doesn’t recognize content in Angular page. So I recommend you to do that.

Furthermore, it sounds strange to add the snippet starting with “<script” tag. However, this was also necessary for me to work.

You can check adClient and adSlot and snippet in your adsense page.

To see your snippet, click the <> button at the bottom right of the screen below.

Also, to change the style of your ad from responsive to fixed size, press the pencil mark to fix it from responsive.

It it my pleasure to visit following demo site.

https://adsense-demo.np-sys.com

Thank you.

Angular(Ionic)で相方向バインディングを行う最低限のテンプレート

1

Formの場合はこちら

https://ionicframework.com/docs/v3/developer-resources/forms/

Note: If you use ngModel within a Form tag, you have to provide a name property. If you do not, you must set standalone to true in ngModelOptions

概要

Angularでは、相方向バインディングを行うための二つの方法があります。一つはテンプレート駆動型、もう一つはリアクティブフォールによるものです。

こちらの公式

https://angular.jp/guide/forms-overview

によると、

  • リアクティブフォーム はより堅牢です。よりスケーラブルで、再利用しやすく、そしてテストがしやすいです。フォームがアプリケーションの重要なパーツである場合、またはアプリケーションの構築にリアクティブパターンをすでに使用している場合は、リアクティブフォームを使用してください。
  • テンプレート駆動フォーム は、メーリングリストの申し込みフォームなどの単純なフォームをアプリに追加するのに役立ちます。アプリに追加するのは簡単ですが、リアクティブフォームほどスケーラビリティはありません。テンプレートでのみ管理できるとても基本的なフォーム要件とロジックをもつような場合は、テンプレート駆動フォームを使用してください。

ということです。

最低限のリアクティブフォーム

使いたいページのmodule、tsファイル、HTMLの3つに変更を加えます。

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { Tab1p5PageRoutingModule } from './tab1p5-routing.module';
import { Tab1p5Page } from './tab1p5.page';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    IonicModule,
    Tab1p5PageRoutingModule,
    ReactiveFormsModule
  ],
  declarations: [Tab1p5Page]
})
export class Tab1p5PageModule {}
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-tab1p5',
  templateUrl: './tab1p5.page.html',
  styleUrls: ['./tab1p5.page.scss'],
})
export class Tab1p5Page implements OnInit {
  favoriteColorControl:formControl;

  constructor() { }

  ngOnInit() {
    this.favoriteColorControl = new FormControl('');
  }

}
<ion-header>
  <ion-toolbar>
    <ion-title>tab1p5</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  Favorite Color: <input type="text" [formControl]="favoriteColorControl">
{{favoriteColorControl.value}}
</ion-content>

Angularでfirestoreのコレクションからデータ一覧を取得する

1

概要

Angular2以降でfirestoreのコレクションからデータ一覧を取得する方法。

読み込む

コレクションに対して、valueChanges()をsubscribeする。valueChanges()ってなんだ。。

import { Component, ViewChild } from '@angular/core';
import { AuthService } from '../auth/auth.service';
import { AngularFirestore } from '@angular/fire/firestore';

@Component({
  selector: 'app-tab2',
  templateUrl: 'tab2.page.html',
  styleUrls: ['tab2.page.scss']
})
export class Tab2Page {
  authUid:string;

  constructor(
    public auth: AuthService,
    private firestore:AngularFirestore,
  ) {
    this.initialize();
    }

  initialize(){
      this.auth.getAuthUid().subscribe(uid =>{
        console.log(uid,'e');
        this.authUid = uid;
      })

  }

  test(){
    this.firestore.collection('users')
      .doc('data').collection(this.authUid)
      .valueChanges().subscribe(value =>{
        console.log(value);
      },
      error =>{
        console.log('error');
      })
  };
}

オブジェクトの配列で帰ってくる。

書き込み

こちらの方がシンプル

                      this.firestore.collection('users')
                        .doc('data')
                        .collection(this.authUid)
                        .doc(String(new Date().getTime()))
                        //.doc('test')
                        .set({
                          'title':this.originalImgName,
                          'content':data,
                          'filename':this.originalImgName,
                          'lastUpade':String(new Date().getTime()),
                        });

IonicのAndroidアプリをWebアプリケーションとしてデプロイする

1

概要

Angular,IonicをWebアプリとしてデプロイする方法について記述する。

firebaseを使ってホスティングする方法と、Xserverでapacheでホスティングする方法を試した。firebaseでホスティングするのは(慣れてしまえば)至れり尽くせりであるが、先のことを考えると値段が気になるためXserverのほうが望ましいように感じた。

Xserverで既にドメインを持っていれば、そのサブドメインにデプロイすることで追加負担0でサイトをデプロイすることができる。速度も速いし、こちらのほうがいい。

Firebaseでのホスティング

https://qiita.com/M_Faust/items/a514b8a79d2fd13cb72b

https://qiita.com/daikiojm/items/89f46bd83c9a2285bbc6

が参考になった。

まず、ライブラリのインストールをする

npm install -g firebase-tools

ログインする

firebase login

なお、.firebasercというファイルが生成されているので2度目以降はこれを消す。macならcommand + shift + . で隠しファイルを表示できる

初期設定を行う

firebase init

なにをしたいか聞かれるので
Hosting: Configure and deploy Firebase Hosting sitesをスペースで選択する。

What do you want to use as your public directory?

と聞かれたら、アップロードしたいファイル一式があるフォルダを指定する。私の場合はIonicだったのでwwwだった。

Configure as a single-page app (rewrite all urls to /index.html)? Yes

と言う質問については、Ionicなのでyest

File www/index.html already exists. Overwrite? (y/N)

これは、先のフォルダで指定した場所のindex.htmlをこの場でテスト用のものに上書きしますかと言うことなので、Noをしておく

その後、

firebase deploy

とすればいい。

Xserverの場合

流れは、xserverのファイルマネージャで確認できる場所へファイルをFTPソフトを使って自分でアップロードし、apacheの設定を.httpaccessとhtconfで行う

https://www.xserver.ne.jp/login_file.php

私はfirezillaを使った。

ionic build --prod

で生成されるファイル郡をfilezillaでアップロードする。私の場合は、サブドメインだったので、サブドメイン用のフォルダ(pwaと言うもの)にアップロードした

その後、pwaの中に.httpaccessファイルを生成し、さらにルートフォルダ(public_html)にhttpd.confを作成し、編集した

https://mi12cp.hatenablog.com/entry/2018/08/25/224149
<Directory "/pwa">
#   Options Indexes FollowSymLinks
    Options FollowSymLinks

#   AllowOverride None
    AllowOverride All

    Require all granted
</Directory>

.htaccessは下記

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^index\.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . index.html [L]
</IfModule>

Angular(Ionic)でStripeでクレカ決済を導入する

目的

Angular(Ionic)でStripeでクレカ決済を導入するにあたり、ソフト側の設定方法をまとめる。AndroidのPlay Storeでの設定方法は下記参照。

概要、考え方

・クライアント側とサーバ側で処理が必要になる。

Stripeのダッシュボードにいくと、公開キーと秘密キーが得られる。

支払いを完了するさい、まずはサーバ側で支払い情報を作っておく。curlでとりあえずはいける。

curl https://api.stripe.com/v1/payment_intents \
  -u sk_test_秘密キー \
  -d amount=100 \
  -d currency=jpy

そうすると、返り値に下記が得られる。

ここにあるClient_Secretと言うものがあるのでこれを使って、ConfirmPaymentメソッドで支払いをクライアントを行う。

https://stripe.com/docs/js/payment_intents/confirm_card_payment

Angular(Ionic)でAdsenseを導入する

1

AngularでAdsenseで広告を出す方法です。

AndroidやiOSアプリで広告を出すためにはAdmobが利用可能ですが、AdmobはWebプラットフフォームをサポートしておりませんのでWebでは使えません。Webの場合はAdsenseを使う必要があります。

ところがAngularでAdsenseを利用する方法はほとんど情報がなく、ましてやIonicとなると皆無でした。非常に苦労しましたが、なんとかわかってきたのでまとめます。

まとめ

ng2-adsenseを使います。しかし公式の通りにやっても動作しませんので、追加でHTMLページにコードを追加する必要があります。また、広告をレスポンシブにしない、ページの上部に設置するといった一見ナンセンスなことに注意する必要があります。

環境

Angular11でIonic6です。

Ionic:

   Ionic CLI                     : 6.13.1 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.6.0
   @angular-devkit/build-angular : 0.1102.4
   @angular-devkit/schematics    : 9.1.6
   @angular/cli                  : 11.2.4
   @ionic/angular-toolkit        : 2.2.0

Capacitor:

   Capacitor CLI   : 2.1.2
   @capacitor/core : 2.1.2

Utility:

   cordova-res                          : not installed
   native-run (update available: 1.3.0) : 1.0.0

System:

   NodeJS : v12.18.0 (/usr/local/bin/node)
   npm    : 6.14.6
   OS     : macOS Big Sur

ng2-adsenseのバージョンは9.01でした。

手順

https://github.com/scttcper/ng2-adsenseからプラグインをInstallします

npm install ng2-adsense

Angularとバージョンを合わせる必要がありますので、公式のGitHubから自分が利用するAngularにあったバージョンをインストールしてください。バージョンを指定する場合は@の後にバージョンを指定します。

npm install ng2-adsense@9.1.0

次に、Index.htmlの<head></head>に下記のスニペットを追加します。

<script async src=//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js></script>

こんな感じになります。Angular、Ionicでscriptタグを利用することはほとんどありませんが、index.htmlなら貼り付けても動作しますので下記のようにしてください。

次に、広告を導入したいモジュールにコードを追加していきます。公式ではapp.module.tsに追加と書いてありますが、使いたいモジュールで追加してください。例えば下記のようなhome.module.tsを編集します。

import { AdsenseModule } from 'ng2-adsense';
@NgModule({
    imports: [
    <Other modules>,
      AdsenseModule.forRoot({
        adClient: 'ca-pub-XXXXXXXXXXXXXX',
        adSlot: XXXXXXXXXXX,
      }),

AdClientなどはAdsenseのサイトから確認していただけると思います。

home.page.tsには何も追加しなくていいです。home.page.htmlを次に編集します。home.page.htmlのion-contentタグの一番上に”<script”から始まる部分を追加します。

  </ion-header>
<ion-content>

<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
  <!-- ocr-app -->
  <ins class="adsbygoogle"
       style="display:inline-block;width:728px;height:90px"
       data-ad-client="ca-pub-XXXXXXXXXXXXXX"
       data-ad-slot="XXXXXXXXX"></ins>
  <script>
       (adsbygoogle = window.adsbygoogle || []).push({});
  </script>

  <ng-adsense></ng-adsense>

注意点は下記です。

  • 広告のStyleはレスポンシブにせず、固定(inline-block;width:728px;height:90pxで動作確認)にする
  • ng-adsenseタグだけではなく、スクリプトタグなど部分も追加する
  • ion-contentタグの一番上に追加すること

です。これを見つけ出すのに非常に苦労しました。

広告のStyleはレスポンシブにすると表示されなくなります。ion-contentタグの一番上に追加することも似たように少しおかしな内容ですが、おそらくAdsense側でAngularのページは認識していないのだと思います。したがってページの途中に「最適な」形で読み込ませようとしてもそれが叶わないので表示されないのだと思います。それがレスポンシブにしたりページ途中に入れるとダメになることではないかと。

一方、ng-adsenseタグだけではなく、”<script”から始まる部分も追加するというのは意味がわかりません。AngularでHTMLにスクリプトを埋め込んでそれが機能するので意味がわかりませんが、動作させるためには必要でした。なんでサニタイズしなくてもいいのかは全くわかりませんが、動作はしました。

なお、adClientとadSlot、追加用のスクリプトなどはadsenseのホームページから確認可能です。

スクリプトの確認のためには下記画面の右下の<>のボタンをクリックしてください。

また、広告のスタイルを固定するためには、鉛筆のマークを押してレスポンシブから固定にします。

まじで苦労してけど表示されるようになった。

https://ocr-app.np-sys.com/

AngularでSEOを考えたときに考慮すること

背景

AngularでSEOに配慮する場合、何に気をつければいいかGoogle Developers Codelabsで勉強したのでその内容を記載します!

https://codelabs.developers.google.com/codelabs/making-a-single-page-app-search-friendly

Google Developers CodelabはハンズオンでGoogleのサービスが学べるもの

https://codelabs.developers.google.com/

1.まずはモバイルフレンドリーかチェックせよ

モバイルファーストと言われて久しいですが、まずはモバイルフレンドリーかチェックをするように記載されていました。

便利なサイトが紹介されていて、このサイトでリンク貼り付ければ調べられます

https://search.google.com/test/mobile-friendly

2.ページ毎にタイトルとメタデータを設定せよ

2番目に紹介されていたのは、基本的なことだが、ページにタイトルを付与し、メタデータディスクリプションを設定することだった。確かにAngularで作成する際、設定したことがなかった。勉強になった。AngularのConponentに直接タイトルとメタデータディスクリプションは設定できないとのことなので、BrowserModuleを使って設定するとよさそう。

https://www.atmarkit.co.jp/ait/articles/1802/05/news142.html

https://www.atmarkit.co.jp/ait/articles/1803/30/news135.html

3.Googlebotが見つけられるようにリンクを記載する

google botがURLを見つけられるようにリンクをちゃんと記載する。

Codelabにはこう記載があった。

A few tips on links

As a rule of thumb, to make sure Googlebot finds all views, make sure that:

  • All links are implemented as <a href="">
  • All links have a valid URL as their href attribute (no javascript: URLs)
  • All views have a URL that doesn’t use the fragment identifier to load different content (no URLs like “/#example” or “/#!example“, sometimes called a hash URL)

要するにハッシュタグを使わないこと、aタグを使うようにとのこと、JavaScriptでレンダリングしないようにするとのことだった。

例として出されていたのは下記

<button class="mdc-button mdc-button--outlined">
  <span class="mdc-button__label">Learn more</span>
</button>

はこう書く

<a class="mdc-button mdc-button--outlined">Learn more</a>

対応するJavaScriptはこレから

item.querySelector('button').addEventListener('click', function () {
  location.href = story.link;
});

これに変える

item.querySelector('a').setAttribute('href', story.link);

このサイトでパフォーマンスを調べると良いとのこと。

https://developers.google.com/web/tools/lighthouse/

エラー時における処理

ページが見つからない場合、リダイレクトするようにする

https://codelabs.developers.google.com/codelabs/making-a-single-page-app-search-friendly/#6

所感

大変勉強になった。AngularだからSEOが不利というわけでもなさそうです。

Angular(Ionic)をWebアプリケーションとしてapache(Xserver)でデプロイする

1

XserverでAngular(Ionic)をホスティングする方法です。

流れとしては

  • サブドメインを設定
  • そのサブドメインにionic build –prodで生成したファイルをアップロードする
  • そのサブドメインのルートに.htaccessを設置する
  • 常時SSL化する
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^index\.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . index.html [L]
</IfModule>

https://awesome-rainbow-colors.com/2020/07/25/ionic%E3%81%AEandroid%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92web%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%A8%E3%81%97%E3%81%A6%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4/

常時SSL化する

常時SSL化するためには、2つのことを行います

https://www.xserver.ne.jp/manual/man_server_fullssl.php

まず、無料独自SSLを導入します。これでHTTPSで接続するとSSL通信できるようになります。

さらに、HTTPで接続した時に自動でリダイレクトするように.htaccessに下記を追加します。

RewriteEngine On
RewriteCond %{HTTPS} !on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

なので最終的には、下記の.htaccessを作成して、サブドメインのプロジェクトのルートフォルダに設置すればいいです。

RewriteEngine On
RewriteCond %{HTTPS} !on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^index\.html$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . index.html [L]
</IfModule>

Ionic(Angular)でAdmobを導入する方法(iOS編)

1

Ionic(Angular)でAdmobを導入する方法です。こちらはiOSでの実装となります。Androidでの実装方法はこちら。

ポイント

  • capacitor-admobプラグインを使用します
  • 公式の方法+Plugin.swiftの編集が必要です(この記事に方法あります)

流れ

  1. npmでcapacitor-admobをインストールし、info.plistを編集します
  2. そのままだとエラーが起こるので、Plugin.swiftを修正します
  3. app.component.tsでアプリ情報を初期化してから読み込みたいモジュールで広告呼び出します

動作環境

Ionic4, 5, 6でiOS13から14.3で確認しています。

参考文献

https://github.com/rahadur/capacitor-admob

https://developers.google.com/admob/android/test-ads?hl=ja

初期状態

IonicのTabsのデフォルトテンプレートから始めます。

作成方法はこちら。


見た目はこんな感じ。

appフォルダ以下の構成は下記のようになっています。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
├── tabs-routing.module.ts
├── tabs.module.ts
├── tabs.page.html
├── tabs.page.scss
├── tabs.page.spec.ts
└── tabs.page.ts

実装する

1. ライブラリをインストールする

npmでcapacitor-admobをインストールします。

 npm install --save capacitor-admob

2. iOSのプロジェクトを生成する

iOSのプロジェクトを生成するために下記コマンドを打ちます。capacitorを入れていない方はcapacitorを入れてください。

ionic build
npx cap add ios
npx cap sync
npx cap open ios

3. info.plistを編集する

info.plistとは、iOSアプリの設定情報などを記入するものです。インフォプロパティリストと読みます。下記コマンドでXcodeを開きます。

npx cap open ios

info.plistを選択します。

このファイルを編集するのですが、編集するにはコツが入ります、info.plistの上で右クリックをして、open as > source codeを選択して編集できるようにします。

そして、下記のスニペットを下の方に貼り付けます。自分のアプリIDに変更するのを忘れないでください。

    <key>GADIsAdManagerApp</key>
    <true/>
    <key>GADApplicationIdentifier</key>
    <!-- replace this value with your App ID key-->
    <string>ca-app-pub-7171076285090910~XXXXXXXX</string>

もし場所がわからない方がいたらinfo.plistを丸ごと貼り付けるので参考にしてください。下から3行目から8行目までが貼り付けたものです。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleDisplayName</key>
        <string>admob-master</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleURLName</key>
			<string>com.getcapacitor.capacitor</string>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>capacitor</string>
			</array>
		</dict>
	</array>
	<key>CFBundleVersion</key>
	<string>1</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>NSAppTransportSecurity</key>
	<dict>
		<key>NSAllowsArbitraryLoads</key>
		<true/>
	</dict>
	<key>NSCameraUsageDescription</key>
	<string>To Take Photos and Video</string>
	<key>NSLocationAlwaysUsageDescription</key>
	<string>Always allow Geolocation?</string>
	<key>NSLocationWhenInUseUsageDescription</key>
	<string>Allow Geolocation?</string>
	<key>NSMicrophoneUsageDescription</key>
	<string>To Record Audio With Video</string>
	<key>NSPhotoLibraryAddUsageDescription</key>
	<string>Store camera photos to camera</string>
	<key>NSPhotoLibraryUsageDescription</key>
	<string>To Pick Photos from Library</string>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UIRequiredDeviceCapabilities</key>
	<array>
		<string>armv7</string>
	</array>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UISupportedInterfaceOrientations~ipad</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationPortraitUpsideDown</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UIViewControllerBasedStatusBarAppearance</key>
	<true/>
    
    <key>GADIsAdManagerApp</key>
    <true/>
    <key>GADApplicationIdentifier</key>
    <!-- replace this value with your App ID key-->
    <string>ca-app-pub-7171076285090910~3362455272</string>
    
</dict>
</plist>

5. 公式にはないエラー処理のコードを書く

公式ではこれで完了ということですが、これだとエラーが起きます。これでエミュレータを起動すると下記のエラーが出ます。

右上を見ると赤い背景で9というエラーが表示されています。その右下の別の赤いボタンからエラーが起きている「Plugin.swift」へ移動します。

そうするとそれぞれのエラーの詳細が確認できます。

よく見るとどれも同じメッセージのようです。こう書いてあります、

Method 'interstitialDidReceiveAd' must be declared public because it matches a requirement in public protocol 'GADInterstitialDelegate'

publicで宣言せよと書いてあるのでそうします。

修正前

    private func interstitialDidReceiveAd(_ ad: GADInterstitial) {
        print("interstitialDidReceiveAd")
        self.notifyListeners("onAdLoaded", data: ["value": true])
    }

修正後

    public func interstitialDidReceiveAd(_ ad: GADInterstitial) {
        print("interstitialDidReceiveAd")
        self.notifyListeners("onAdLoaded", data: ["value": true])
    }

合計9箇所同じように変えると、ビルドできるようになるはずです。

広告を表示する

以上で広告を表示するためのプラグインの設定が終わったので、いよいよ広告をアプリで表示していきます。

まず、app.component.tsでアプリケーションを初期化します。

import { Plugins } from "@capacitor/core";
// import { initialize } from 'capacitor-admob'; No longar required

const { AdMob } = Plugins;

@Component({
  selector: "app-root",
  templateUrl: "app.component.html",
  styleUrls: ["app.component.scss"]
})
export class AppComponent {
  constructor() {
    // Initialize AdMob for your Application
    AdMob.initialize("YOUR APPID");
  }
}

次に、読み込みたいページで任意の広告を呼び出します。ここでは、tab1でバナー広告、tab2でインタースティシャル広告を呼び出します。

テストIDを使ってください。

https://developers.google.com/admob/android/test-ads?hl=ja

tab1でバナー広告を呼び出す

import { Component } from '@angular/core';
import { Plugins } from "@capacitor/core";
import { AdOptions, AdSize, AdPosition } from "capacitor-admob";

const { AdMob } = Plugins;
@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
  options: AdOptions = {
    adId: "ca-app-pub-3940256099942544/6300978111",
    adSize: AdSize.BANNER,
    position: AdPosition.BOTTOM_CENTER
  };

  constructor() {
    // Show Banner Ad
    AdMob.showBanner(this.options).then(
      value => {
        console.log(value); // true
      },
      error => {
        console.error(error); // show error
      }
    );

    // Subscibe Banner Event Listener
    AdMob.addListener("onAdLoaded", (info: boolean) => {
      console.log("Banner Ad Loaded");
    });
  }

}

tab2でインタースティシャルを表示する

import { Component } from '@angular/core';
import { Plugins } from "@capacitor/core";
import { AdOptions } from "capacitor-admob";
const { AdMob } = Plugins;

@Component({
  selector: 'app-tab2',
  templateUrl: 'tab2.page.html',
  styleUrls: ['tab2.page.scss']
})
export class Tab2Page {
  options: AdOptions = {
    adId: "ca-app-pub-3940256099942544/1033173712",
    hasTabBar: false, // make it true if you have TabBar Layout.
    tabBarHeight: 56 // you can assign custom margin in pixel default is 56
  };

  constructor() {
    // Prepare interstitial banner
    AdMob.prepareInterstitial(this.options).then(
      value => {
        console.log(value); // true
      },
      error => {
        console.error(error); // show error
      }
    );

    // Subscibe Banner Event Listener
    AdMob.addListener("onAdLoaded", (info: boolean) => {
      // You can call showInterstitial() here or anytime you want.
      console.log("Interstitial Ad Loaded");
      // Show interstitial ad when it’s ready

      AdMob.showInterstitial().then(
        value => {
          console.log(value); // true
        },
        error => {
          console.error(error); // show error
        }
      );

    });
  }
}

時間を空ける

Admobの仕様で広告が表示されるまでに時間が必要です。

設定が正しくされていても最初は

  • Ad failed to load : 2
  • Ad failed to load : 0

などが表示されて広告が表示されません。これは時間をおけば解決なので少し忍耐が必要です。

その他何かうまくいかないときは下記を実行することをお勧めします。

  • ionic build –prodでプロダクションモードでビルドする
  • npx cap sync ios
  • npx cap copy

終わりに

あー記事まとめるのめっちゃ大変だった!お疲れ様でした。

Ionic(Angular)でAdmobを導入する方法(Android編)

1

Ionic(Angular)でAdmobを導入する方法です。こちらはAndroidでの実装となります。iOS版はこちら。

ポイント

  • capacitor-admobプラグインを使用します
  • 公式の方法+依存関係の解決(この記事に方法あります)が必要です

流れ

  1. npmでcapacitor-admobをインストールします
  2. そのままだとエラーが起こるので、依存関係を解決します
  3. エミュレータで正しくアプリが立ち上がり依存関係が解決されていることを確認したのち、app.component.tsでアプリ情報を初期化してから読み込みたいモジュールで広告呼び出します

動作環境

Ionic4, 5, 6でAndroid SDKはAPIレベル29(Android X)で検証。28以下でもいけました。

参考文献

https://github.com/rahadur/capacitor-admob

https://developers.google.com/admob/android/test-ads?hl=ja

初期状態

IonicのTabsのデフォルトテンプレートから始めます。

appフォルダ以下の構成は下記のようになっています。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
├── tabs-routing.module.ts
├── tabs.module.ts
├── tabs.page.html
├── tabs.page.scss
├── tabs.page.spec.ts
└── tabs.page.ts

実装する

1. ライブラリをインストールする

npmでcapacitor-admobをインストールします。

 npm install --save capacitor-admob

2. Androidのプロジェクトを生成する

Androidのプロジェクトを生成するために下記コマンドを打ちます。capacitorを入れていない方はcapacitorを入れてください。

ionic build
npx cap add android
npx cap sync
npx cap open android

3. AndroidManifest.xmlを編集する

Androidのマニフェストファイルはアプリに機能を追加するときに編集するものです。Android Studioではデフォルトで作成されるものとなります。

ここからどのファイルを編集するか分かりにくいので詳細に記載します。非常にここは苦労しました。

デフォルトでは下記のようにAndroidが選択されていると思います。

ドリルダウンを選択してProjectを選択します

この状態にまずしてください。

マニフェストファイルはProjectのandroid/app/src/main/AndroidManifest.xmlにあります。

自分のAdmobのアプリIDに編集する必要がありますが、下記のように変更します。

<application>
  <!-- this line needs to be added (replace the value!) -->
         <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-7171076285090910~XXXXXXXX" />
  <activity></activity>
</application>

つまり、編集した後のファイルは下記のようになります。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="io.ionic.starter">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-7171076285090910~XXXXXXXXX" />

        <activity
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
            android:name="io.ionic.starter.MainActivity"
            android:label="@string/title_activity_main"
            android:theme="@style/AppTheme.NoActionBarLaunch"
            android:launchMode="singleTask">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="@string/custom_url_scheme" />
            </intent-filter>

        </activity>

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"></meta-data>
        </provider>
    </application>

    <!-- Permissions -->

    <uses-permission android:name="android.permission.INTERNET" />
    <!-- Camera, Photos, input file -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!-- Geolocation API -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-feature android:name="android.hardware.location.gps" />
    <!-- Network API -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!-- Navigator.getUserMedia -->
    <!-- Video -->
    <uses-permission android:name="android.permission.CAMERA" />
    <!-- Audio -->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
</manifest>

4. MainActivity.javaを編集する

admobをandroidに登録します。

Projectを選択した状態で、Android>app>src>main>java><your original name>>MainActivityを選択します。

そして、このように変更します。

// Other imports...
import app.xplatform.capacitor.plugins.AdMob;

public class MainActivity extends BridgeActivity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    this.init(savedInstanceState, new ArrayList<Class<? extends Plugin>>() {{

      add(AdMob.class);  // Add AdMob as a Capacitor Plugin

    }});
  }
}

つまり、このようにします。

package io.ionic.starter;

import android.os.Bundle;

import com.getcapacitor.BridgeActivity;
import com.getcapacitor.Plugin;
import java.util.ArrayList;

import app.xplatform.capacitor.plugins.AdMob;

public class MainActivity extends BridgeActivity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Initializes the Bridge
    this.init(savedInstanceState, new ArrayList<Class<? extends Plugin>>() {{
      // Additional plugins you've installed go here
      // Ex: add(TotallyAwesomePlugin.class);
      add(AdMob.class);  // Add AdMob as a Capacitor Plugin

    }});
  }
}

5. 公式にはないエラー処理のコードを書く

公式ではこれで完了ということですが、これだとエラーが起きます。これでエミュレータを起動すると下記のエラーが出ます。

/Users/masaya/Desktop/MyGoodness/admob-master/admob-master/node_modules/capacitor-admob/android/src/main/java/app/xplatform/capacitor/plugins/AdMob.java:4: エラー: パッケージandroid.support.design.widgetは存在しません
import android.support.design.widget.CoordinatorLayout;

そこで、焦らずに下記の対応を行います。

https://github.com/rahadur/capacitor-admob/issues/35

Admob.javaの編集

まず、エラーが出ているAdmob.javaのファイルのエラー発生している行を下記に変更します。

import androidx.coordinatorlayout.widget.CoordinatorLayout;

前のものはコメントアウトでもしておいてこのようにします。

そして、capacito-admobのbuild.gradleのdependenciesの中にimplementation ‘androidx.coordinatorlayout:coordinatorlayout:1.1.0’を追加します。

implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'

編集するファイルを間違えるとドツボにハマるので注意。

これでサイドエミュレータを立ち上げると下記のエラーが出ます。

2020-12-21 15:44:03.866 9236-9236/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: io.ionic.starter, PID: 9236
    java.lang.RuntimeException: Unable to start activity ComponentInfo{io.ionic.starter/io.ionic.starter.MainActivity}: android.view.InflateException: Binary XML file line #2 in io.ionic.starter:layout/bridge_layout_main: Binary XML file line #2 in io.ionic.starter:layout/bridge_layout_main: Error inflating class android.support.design.widget.CoordinatorLayout
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3270)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
     Caused by: android.view.InflateException: Binary XML file line #2 in io.ionic.starter:layout/bridge_layout_main: Binary XML file line #2 in io.ionic.starter:layout/bridge_layout_main: Error inflating class android.support.design.widget.CoordinatorLayout
     Caused by: android.view.InflateException: Binary XML file line #2 in io.ionic.starter:layout/bridge_layout_main: Error inflating class android.support.design.widget.CoordinatorLayout
     Caused by: java.lang.ClassNotFoundException: android.support.design.widget.CoordinatorLayout
        at java.lang.Class.classForName(Native Method)
        at java.lang.Class.forName(Class.java:454)
        at android.view.LayoutInflater.createView(LayoutInflater.java:815)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:1006)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:961)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:659)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:534)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:481)
        at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:555)
        at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:161)
        at com.getcapacitor.BridgeActivity.init(BridgeActivity.java:60)
        at com.getcapacitor.BridgeActivity.init(BridgeActivity.java:48)
        at io.ionic.starter.MainActivity.onCreate(MainActivity.java:17)
        at android.app.Activity.performCreate(Activity.java:7802)
        at android.app.Activity.performCreate(Activity.java:7791)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1299)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
2020-12-21 15:44:03.866 9236-9236/? E/AndroidRuntime: Caused by: java.lang.ClassNotFoundException: Didn't find class "android.support.design.widget.CoordinatorLayout" on path: DexPathList[[zip file "/data/app/io.ionic.starter-wIOzWpenwsyjIPlSvEtd-g==/base.apk"],nativeLibraryDirectories=[/data/app/io.ionic.starter-wIOzWpenwsyjIPlSvEtd-g==/lib/x86, /system/lib, /system/product/lib]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:196)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        	... 28 more

そこで、この辺を解決してくれるように下記のライブラリを導入します。

terminalで下記を打ちます。

npm install jetifier
npx jetify
npx cap sync android

これで再度エミュレータを起動すると、ようやく無事に立ち上がるようになります。これで依存関係が無事に解決されました。

広告を表示する

以上で広告を表示するためのプラグインの設定が終わったので、いよいよ広告をアプリで表示していきます。

まず、app.component.tsでアプリケーションを初期化します。

import { Plugins } from "@capacitor/core";
// import { initialize } from 'capacitor-admob'; No longar required

const { AdMob } = Plugins;

@Component({
  selector: "app-root",
  templateUrl: "app.component.html",
  styleUrls: ["app.component.scss"]
})
export class AppComponent {
  constructor() {
    // Initialize AdMob for your Application
    AdMob.initialize("YOUR APPID");
  }
}

次に、読み込みたいページで任意の広告を呼び出します。ここでは、tab1でバナー広告、tab2でインタースティシャル広告を呼び出します。

テストIDを使ってください。

https://developers.google.com/admob/android/test-ads?hl=ja

tab1でバナー広告を呼び出す

import { Component } from '@angular/core';
import { Plugins } from "@capacitor/core";
import { AdOptions, AdSize, AdPosition } from "capacitor-admob";

const { AdMob } = Plugins;
@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
  options: AdOptions = {
    adId: "ca-app-pub-3940256099942544/6300978111",
    adSize: AdSize.BANNER,
    position: AdPosition.BOTTOM_CENTER
  };

  constructor() {
    // Show Banner Ad
    AdMob.showBanner(this.options).then(
      value => {
        console.log(value); // true
      },
      error => {
        console.error(error); // show error
      }
    );

    // Subscibe Banner Event Listener
    AdMob.addListener("onAdLoaded", (info: boolean) => {
      console.log("Banner Ad Loaded");
    });
  }

}

tab2でインタースティシャルを表示する

import { Component } from '@angular/core';
import { Plugins } from "@capacitor/core";
import { AdOptions } from "capacitor-admob";
const { AdMob } = Plugins;

@Component({
  selector: 'app-tab2',
  templateUrl: 'tab2.page.html',
  styleUrls: ['tab2.page.scss']
})
export class Tab2Page {
  options: AdOptions = {
    adId: "ca-app-pub-3940256099942544/1033173712",
    hasTabBar: false, // make it true if you have TabBar Layout.
    tabBarHeight: 56 // you can assign custom margin in pixel default is 56
  };

  constructor() {
    // Prepare interstitial banner
    AdMob.prepareInterstitial(this.options).then(
      value => {
        console.log(value); // true
      },
      error => {
        console.error(error); // show error
      }
    );

    // Subscibe Banner Event Listener
    AdMob.addListener("onAdLoaded", (info: boolean) => {
      // You can call showInterstitial() here or anytime you want.
      console.log("Interstitial Ad Loaded");
      // Show interstitial ad when it’s ready

      AdMob.showInterstitial().then(
        value => {
          console.log(value); // true
        },
        error => {
          console.error(error); // show error
        }
      );

    });
  }
}

時間を空ける

Admobの仕様で広告が表示されるまでに時間が必要です。

設定が正しくされていても最初は

  • Ad failed to load : 2
  • Ad failed to load : 0

などが表示されて広告が表示されません。これは時間をおけば解決なので少し忍耐が必要です。

その他何かうまくいかないときは下記を実行することをお勧めします。

  • ionic build –prodでプロダクションモードでビルドする
  • npx cap sync android
  • npx cap copy

終わりに

あー記事まとめるのめっちゃ大変だった。お疲れ様でした。

Angularで画像をトリミングする方法

1

Angularで読み込んだ画像をトリミングできるようにする方法です。

ポイント

  • ngx-image-cropperを使います

流れ

  1. ngx-image-cropperをnpmでinstallします
  2. 読み込みたい場所でngx-image-cropperをmoduleに登録します
  3. その場所のHTMLとTSファイルを編集します

環境

Ionic 4, 5, 6で動作検証しています。

ソースコード

GitHubに上げました。

https://github.com/NP-Systems/Ionic_tips/tree/id1137-imageCropper

参考文献

https://www.npmjs.com/package/ngx-image-cropper

初期状態

IonicのTabsのデフォルトテンプレートから始めます。

appフォルダ以下の構成は下記のようになっています。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
├── tabs-routing.module.ts
├── tabs.module.ts
├── tabs.page.html
├── tabs.page.scss
├── tabs.page.spec.ts
└── tabs.page.ts

実装方法

それではTab1ページに実装していきます。

インストール

プロジェクトのフォルダに移動して下記コマンドでインストールします。

npm install ngx-image-cropper --save

モジュールへの登録

今回はtab1に実装することにするので、tab1.module.tsに下記のようにします。

import { IonicModule } from '@ionic/angular';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Tab1Page } from './tab1.page';
import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
import { ImageCropperModule } from 'ngx-image-cropper';

import { Tab1PageRoutingModule } from './tab1-routing.module';

@NgModule({
  imports: [
    IonicModule,
    CommonModule,
    FormsModule,
    ExploreContainerComponentModule,
    Tab1PageRoutingModule,
    ImageCropperModule
  ],
  declarations: [Tab1Page]
})
export class Tab1PageModule {}

import { ImageCropperModule } from ‘ngx-image-cropper’;を追加して、

@NgModuleのimportsにImageCropperModuleを追加します。

HTMLとTSファイルの編集

上記で準備は完了なので、HTMLとTSファイルの編集を行います。

tab1.page.htmlに下記のように追加します、

<input type="file" (change)="fileChangeEvent($event)" />

<image-cropper
    [imageChangedEvent]="imageChangedEvent"
    [maintainAspectRatio]="true"
    [aspectRatio]="4 / 3"
    format="png"
    (imageCropped)="imageCropped($event)"
    (imageLoaded)="imageLoaded()"
    (cropperReady)="cropperReady()"
    (loadImageFailed)="loadImageFailed()"
></image-cropper>

<img [src]="croppedImage" />

tab1.page.tsを下記のようにします。

import { Component } from '@angular/core';
import { ImageCroppedEvent } from 'ngx-image-cropper';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
    imageChangedEvent: any = '';
    croppedImage: any = '';

    constructor(
    ) {}

    fileChangeEvent(event: any): void {
        this.imageChangedEvent = event;
    }
    imageCropped(event: ImageCroppedEvent) {
        this.croppedImage = event.base64;
    }
    imageLoaded(image: HTMLImageElement) {
        // show cropper
    }
    cropperReady() {
        // cropper ready
    }
    loadImageFailed() {
        // show message
    }

}

応用編

以上が基礎ですが、ここからは切り取った画像を新たに別の画像をとして読み込む方法について記載します。

まず、HTMLに切り取った後に押下するためのボタンを配置します。

tab1.page.htmlの当該部分を下記のように変更します。

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Tab 1</ion-title>
    </ion-toolbar>
  </ion-header>

  <ion-item>
    <input type="file" accept='image/*'  (change)="fileChangeEvent($event)" />
  </ion-item>

  <ion-progress-bar type="indeterminate" *ngIf="show" reversed="true"></ion-progress-bar>
  <!--プログレスバーの追加です-->

  <image-cropper
  [imageChangedEvent]="imageChangedEvent"
  [maintainAspectRatio]="false"
  [aspectRatio]="5/ 3"
  format="jpeg"
  (imageCropped)="imageCropped($event)"
  (imageLoaded)="imageLoaded()"
  (cropperReady)="cropperReady()"
  (loadImageFailed)="loadImageFailed()"
  imageQuality=25
></image-cropper>

  <img [src]="croppedImage" />

  <ion-button expand='block' (click)="onButtonClicked()" shape='round'>Get cropped image</ion-button>

</ion-content>

そして、tsファイルで追加で下記2つのモジュールを読み込みます。

import { LoadingController } from '@ionic/angular';
import { Observable } from 'rxjs';

'
'
  constructor(
    public loadingController: LoadingController,
  ) {}

そして、tsファイルの変数として下記を追加します。

export class Tab1Page {
  imageChangedEvent: any = '';
  originalImgName:string;
  buffCroppedImageBase64:string;
  croppedImageBase64: any = '';
  croppedFileObject:any;
  show:any=false;
  • imageChangedEventは、画像の選択範囲が変わったら都度呼び出されるイベントに関する情報を保持するものです。
  • originalImgNameは、inputタグで読み込んだ画像から元のファイル名を取得できるのでそれを格納する変数です
  • buffCroppedImageBase64は、切り取った画像(base64です)を格納する一次ファイルです。HTML側で<img [src]=”croppedImageBase64″ />と定義していますが、ユーザーが選んでいる時に都度croppedImageBase64″に入れると切り取った画像がチラチラしてしまうので、一時的に格納するものとしてbuffCroppedImageBase64を用意しました。
  • CroppedImageBase64は最終的な結果を入れるものです。ここにBase64の画像を入れると、HTML側で表示されます
  • croppedFileObjectは、切り取った画像を別のファイルとして読み込むのに使用します。
  • showはProgressバーを制御するものです

fileChangeEventの部分を下記のように拡張します。

  fileChangeEvent(event: any): void {
    this.show = true;
    this.read(event).subscribe(()=>{
      console.log('done');
      this.show = false;
    });
  }

  read(event: any):Observable<any> {
    return new Observable(observer => {
      setTimeout(() => {
        this.imageChangedEvent = event;
        this.originalImgName = event.target.files[0].name;
        observer.next('e');
      }, 500);
    })
  }

重い画像だと読み込むのに数秒かかってしまいます。そこで、読み込み開始したらprogressバーで動作していることを知らせています、500は使った感触で決めました。

imageCroppedの関数を下記のように変えます。

  imageCropped(event: ImageCroppedEvent) {
      this.buffCroppedImageBase64 = event.base64;
  }

先ほど行ったように一度Bafferの方に切り取った画像を入れています。

最後にonButtonClickedの関数を下記のように変更します。


  onButtonClicked(){
    this.croppedImageBase64 = this.buffCroppedImageBase64;
    console.log(this.croppedImageBase64,'this.croppedImageBase64');

    var bin = atob(this.buffCroppedImageBase64.replace(/^.*,/, ''));
    // バイナリデータ化
    var buffer = new Uint8Array(bin.length);
    for (var i = 0; i < bin.length; i++) {
        buffer[i] = bin.charCodeAt(i);
    }
    this.croppedFileObject = new File([buffer.buffer], "heihei.jpg",{type: "image/jpeg"});
    console.log(this.croppedFileObject,'this.croppedFileObject');
  }

最終的に下記にようにします。

import { Component } from '@angular/core';
import { ImageCroppedEvent } from 'ngx-image-cropper';
import { LoadingController } from '@ionic/angular';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
  imageChangedEvent: any = '';
  originalImgName:string;
  buffCroppedImageBase64:string;
  croppedImageBase64: any = '';
  show:any=false;

  constructor(
    public loadingController: LoadingController,
  ) {}

  fileChangeEvent(event: any): void {
    console.log(event,'abcde');
    this.show = true;
    this.read(event).subscribe(()=>{
      console.log('done');
      this.show = false;
    });
  }

  read(event: any):Observable<any> {
    return new Observable(observer => {
      setTimeout(() => {
        this.imageChangedEvent = event;
        this.originalImgName = event.target.files[0].name;
        observer.next('e');
      }, 500);
    })
  }


  imageCropped(event: ImageCroppedEvent) {
      this.buffCroppedImageBase64 = event.base64;
  }
  imageLoaded(image: HTMLImageElement) {
      // show cropper
  }
  cropperReady() {
      // cropper ready
  }
  loadImageFailed() {
      // show message
  }

  onButtonClicked(){
    this.croppedImageBase64 = this.buffCroppedImageBase64;
    console.log(this.croppedImageBase64,'this.croppedImageBase64');

    var bin = atob(this.buffCroppedImageBase64.replace(/^.*,/, ''));
    // バイナリデータ化
    var buffer = new Uint8Array(bin.length);
    for (var i = 0; i < bin.length; i++) {
        buffer[i] = bin.charCodeAt(i);
    }
    this.croppedFileObject = new File([buffer.buffer], "heihei.jpg",{type: "image/jpeg"});
    console.log(this.croppedFileObject,'this.croppedFileObject');
  }


}

Ionic(Angular)でアコーディオンを実装する方法

1

Ionic(Angular)でアコーディオンを実装する方法です。

ポイント

  • Componentを自作して、それを読み込むようにします
  • これによりいろいろな場所で使い回しができるようになります

結果

こんな感じでクリックするとカードが広がるようになります。

環境

Ionic 4, 5, 6で動作検証しています。

流れ

  1. コンポーネントを作成し、HTMLとSCSSを記載します
  2. 読み込みたい場所で作成したコンポーネントをmoduleに登録します
  3. 使いたい場所のHTMLでapp-expandableタグで呼び出します

ソースコード

GItHubに上げました。

https://github.com/NP-Systems/Ionic_tips/tree/id1131-createAccordion/frontend

参考文献

https://www.joshmorony.com/creating-an-accordion-list-in-ionic/

初期状態

IonicのTabsのデフォルトテンプレートから始めます。

appフォルダ以下の構成は下記のようになっています。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
    ├── tabs-routing.module.ts
    ├── tabs.module.ts
    ├── tabs.page.html
    ├── tabs.page.scss
    ├── tabs.page.spec.ts
    └── tabs.page.ts

自作コンポーネントの作成

下記のコマンドで自作コンポーネントを作成します。

% ionic g component components/Expandable

app.module.tsと同じ階層にcomponentsフォルダができて新しいコンポーネントが生成されます。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── components
│   └── expandable
│       ├── expandable.component.html
│       ├── expandable.component.scss
│       ├── expandable.component.spec.ts
│       └── expandable.component.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
    ├── tabs-routing.module.ts
    ├── tabs.module.ts
    ├── tabs.page.html
    ├── tabs.page.scss
    ├── tabs.page.spec.ts
    └── tabs.page.ts

tab1.module.tsでの読み込み

今回はtab1ページに埋め込むことにするので、tab1.module.tsに下記のように登録します。

import { IonicModule } from '@ionic/angular';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Tab1Page } from './tab1.page';
import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
import { ExpandableComponent } from "../components/expandable/expandable.component";

import { Tab1PageRoutingModule } from './tab1-routing.module';

@NgModule({
  imports: [
    IonicModule,
    CommonModule,
    FormsModule,
    ExploreContainerComponentModule,
    Tab1PageRoutingModule
  ],
  declarations: [Tab1Page,ExpandableComponent]
})
export class Tab1PageModule {}

expandable.component.htmlの変更

 src/components/expandable/expandable.component.htmlのファイルを下記のように変更します。

<p>
  expandable works!
</p>

<div #expandWrapper class='expand-wrapper' [class.collapsed]="!expanded">
    <ng-content></ng-content>
</div>

expandable.component.scssの変更

.expand-wrapper {
  transition: max-height 0.4s ease-in-out;
  overflow: hidden;
  height: auto;
}

.collapsed {
  max-height: 0 !important;
}

expandable.component.tsの変更

Before

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-expandable',
  templateUrl: './expandable.component.html',
  styleUrls: ['./expandable.component.scss'],
})
export class ExpandableComponent implements OnInit {

  constructor() { }

  ngOnInit() {}

}

After

import { Component, AfterViewInit, Input, ViewChild,  ElementRef, Renderer2 } from "@angular/core";

@Component({
  selector: "app-expandable",
  templateUrl: "./expandable.component.html",
  styleUrls: ["./expandable.component.scss"]
})
export class ExpandableComponent implements AfterViewInit {
  @ViewChild("expandWrapper", { read: ElementRef }) expandWrapper: ElementRef;
  @Input("expanded") expanded: boolean = false;
  @Input("expandHeight") expandHeight: string = "150px";

  constructor(public renderer: Renderer2) {}

  ngAfterViewInit() {
    this.renderer.setStyle(this.expandWrapper.nativeElement, "max-height", this.expandHeight);
  }
}

tab1.page.htmlの変更

Before

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Tab 1
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Tab 1</ion-title>
    </ion-toolbar>
  </ion-header>

  <app-explore-container name="Tab 1 page"></app-explore-container>
</ion-content>

After

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Tab 1
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-card (click)="expandItem(item)" *ngFor="let item of items">
    <ion-card-header>
      <ion-card-title>隣のトトロ</ion-card-title>
    </ion-card-header>

    <ion-card-content>
      <app-expandable expandHeight="100px" [expanded]="item.expanded">
        <p>
          とっとろ、と、とーろ
        </p>
        <p>
          とっとろ、と、とーろ
        </p>
      </app-expandable>
    </ion-card-content>
  </ion-card>
</ion-content>

tab1.page.tsの変更

Before

import { Component } from '@angular/core';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {

  constructor() {}

}

After

import { Component } from '@angular/core';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
  public items: any = [];

  constructor() {
    this.items = [
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false },
      { expanded: false }
    ];
  }

  expandItem(item): void {
    if (item.expanded) {
      item.expanded = false;
    } else {
      this.items.map(listItem => {
        if (item == listItem) {
          listItem.expanded = !listItem.expanded;
        } else {
          listItem.expanded = false;
        }
        return listItem;
      });
    }
  }
}

自社内アプリを開発する私がIonicを気に入っている理由

1

Ionicとはなにか

会社の中で使う業務アプリをよくlonic(Angular)で作っています。ここでは、自社内アプリを開発する私がIonicを気に入っている理由を解説します。

一応Ionicについて説明しておくと、

  • HTMLやJavascript(TypeScript)系のWeb技術を使ってAndroid, iOSのスマホアプリやWindows,Mac向けのデスクトップアプリを作るフレームワーク
  • 「Web Components」というWeb標準の技術が採用されており、もっとも推奨されているのはAngularですが、ReactやVueからも利用可能

結論

先に結論書くとIonicを使う理由は

  • UI構築が楽
  • ページ遷移時の動作が良い
  • バンドルサイズが小さい
  • Capacitorと連携してNativeの機能にアクセスできる

からです。

UI構築が楽

自社内アプリなので、デザインは凝らなくていいんですよね。ださくなければいいです。以前Cordovaでスマホアプリを開発していましたのですが、さすがにHTMLとJavaScriptで記述したWebページがそのままAndroid, iOSで動作するとダサいです。

そ決してCordovaが悪いわけではないのですが、HTMLとJavaScriptで記述したWebページがそのままアプリになる以上、どうしてもダサいです。例えばボタン一つとってもbootstrapを使えばWebでは綺麗に見えるのにアプリで見るとなんか違う感が拭えなかったです。Monacaのonsen.uiというものでNativeの見た目を再現できるということでしたが、個人的な感想としてMonacaは嫌いでした。

  • ビルドがあまりにも遅すぎてPDCAが回せない
  • 有料でも作成できるプロジェクト数が不便を感じるほど限られている
  • Monacaという名称やonsen.uiという名前がときめかない
  • Monacaで一度作ると、Monaca上でしかメンテナンスできなくなる

というデメリットを感じ使用を中止しました。特に最後の「Monacaで一度作ると、Monaca上でしかメンテナンスできなくなる」というのは強烈で、有料プランでエンパワーメントしてもらえると思っていたのに、実際は囲い込まれているということで非常に残念に思いました。

その点、IonicはiOS/Android別の美しいUIを提供してくれます。IonicがAngularやReactから使えることからわかるように、Ionicの本質はUIコンポーネントのライブラリです。あるコンポーネント(例えばボタン)を設置したとしましょう。そうすると、iOSで表示した場合はAppleのHuman Interface Guidelinesに基づいたデザイン、Androidで表示した場合はマテリアルデザインと自動的に表示が切り替わります。これは実際に書いていて最高にウキウキするIonicの素晴らしいところです。

また、こちらの記事(Cordovaをdisる人類全員に読んでほしい「Cordovaつらいを考える」)に記載されているように、60fpsでの表示で滑らかに動くことや、無限スクロールが可能なことも拡張性を保証する大事なポイントだと考えています。

ページ遷移時の動作が良い

最近、スマホ画面の下にTabがあるアプリケーションが多くあります。Cordovaでも当然実装できるのですが、音楽を入れた時にページが切り替わるたび音楽が初めから再生されるという非常に残念な思いをしたことがあります。

その点、IonicはTabを切り替えてもバックグラウンド音楽が途切れない。。Nativeで開発しているわけではないのに、Nativeで違うことで悩まされないというのは地味ですが大きいと考えています。

この辺を非線形ナビゲーション」と「ナビゲーションスタック」というらしいですが、Ionicのドキュメントからコアコンセプトのページがなくなっていたので詳細をしらべることができませんでした。

バンドルサイズが小さい

特にAndroidでは顕著ですが、アプリケーションのバンドルサイズは非常に重要視されています。Androidアプリを開発されている方はご存知かもしれませんが、近年、Androidアプリの拡張子は.apkではないのです。デベロッパーはaboというプレビルドしたような形でアップロードしておいて、ユーザーがインストールする際、そのデバイスに最適化したapkをGoogleが生成してダウンロードします。日本のように通信環境が整っていない国も多いなかで、バンドルサイズを小さくすることがいかに重要視されているかわかると思います。

その点、lonicは、「Stencil」というWeb Components開発ライブラリによって作られており、バンドルサイズはとても小さくなります。

また、LazyLoadingという最初に全てを読み込むわけではなく、必要なものはあとで読み込む機能を実装しているので、軽量に高速で動作します。LazyLoadingはAngularにも実装されているので迷うかもしれませんが、こちらに英語ですがIonic X AngularにおけるLazy Loadingの実装方法について記事がまとまっています。このように公式が充実しているのもIonicのいいところです。https://ionicframework.com/blog/how-to-lazy-load-in-ionic-angular/

なお、Angularについて解説した記事はこちらですので、Angularについて知りたい方はどうぞ。

https://np-sys.com/general/%e5%80%8b%e4%ba%ba%e3%83%bb%e5%b0%91%e4%ba%ba%e6%95%b0%e3%81%ae%e3%82%b7%e3%82%b9%e3%83%86%e3%83%a0%e9%96%8b%e7%99%ba%e3%81%ab%e3%81%afangular%e3%81%8c%e3%82%aa%e3%82%b9%e3%82%b9%e3%83%a1/

Nativeの機能にアクセスできる

Ionicは(Cordovaもそうですが)Webページをアプリにできるだけではなく、そこにNativeのカメラ、位置情報などのAPIにアクセスできることは大きな魅力です。Ionicにはさまざまなプラグインがありますので、これを利用することでWeb技術を使いながら、それを超えたアプリケーションを作成することが可能です。

Ionicプラグインの導入方法

総括、その他

 Ionicについて簡単に解説しました。拡張性が担保されたフレームワークでプロジェクトをひとつ作り、それをそれぞれの最高のUIでクロスプラットフォームに出力できる点が一番気に入っています。

AngularでIonicを用いてtab barのデザインをカスタマイズする

1

Angualrとクロスプラットフォームアプリ開発用のフレームワークであるIonicを組み合わせて、Tab barをカスタマイズする方法を解説します。

ポイント

  • Tab barを備えたプロジェクトはIonicが提供しているテンプレートを使用
  • 比較的コードは単純で実装は容易

Github

Githubにコードあげています。

https://github.com/NP-Systems/Ionic_tips/tree/id-1113-customize-tab-bar

実装手順

Ionicが提供しているTab barを備えたテンプレートをダウンロードします。

% ionic start

Pick a framework! ?

Please select the JavaScript framework to use for your new app. To bypass this
prompt next time, supply a value for the --type option.

? Framework: Angular

Every great app needs a name! ?

Please enter the full name of your app. You can change this at any time. To
bypass this prompt next time, supply name, the first argument to ionic start.

? Project name: myApp

そうするとつらつら自動でダウンロードしてくれるので、完了後作成したフォルダへ移動します。

% cd myApp

フォルダ構成

tabsコンポーネント

tabs.page.htmlとtabs.page.scssの二つを変更します。

tabs.page.html

修正前

<ion-tabs>

  <ion-fab vertical="bottom" horizontal="center" translucent="true">
    <ion-fab-button (click)="goToPictures()">
      <ion-icon name="camera"></ion-icon>
    </ion-fab-button>
  </ion-fab>

  <ion-tab-bar slot="bottom" class="ion-no-border">


    <ion-tab-button tab="tab-conversations" class="comments">
      <ion-icon name="triangle"></ion-icon>
      <ion-label>Tab 1</ion-label>
      <ion-badge >Hi</ion-badge>
    </ion-tab-button>

    <svg height="50" viewBox="0 0 100 50" width="100" xmlns="http://www.w3.org/2000/svg">
      <path d="M100 0v50H0V0c.543 27.153 22.72 49 50 49S99.457 27.153 99.99 0h.01z" fill="red" fill-rule="evenodd">
      </path>
    </svg>

    <ion-tab-button tab="tab-notifications" class="notifs">
      <ion-icon name="notifications"></ion-icon>
      <ion-badge *ngIf="new_activities">{{new_activities}}</ion-badge>
    </ion-tab-button>


  </ion-tab-bar>
</ion-tabs>

修正後

<ion-tabs>

  <ion-fab vertical="bottom" horizontal="center" translucent="true">
    <ion-fab-button (click)="yourCustomFunction()">
      <ion-icon name="camera"></ion-icon>
    </ion-fab-button>
  </ion-fab>

  <ion-tab-bar slot="bottom" class="ion-no-border">

    <ion-tab-button tab="tab-conversations" class="comments">
      <ion-icon name="triangle"></ion-icon>
      <ion-label>Tab 1</ion-label>
      <ion-badge >Hi</ion-badge>
    </ion-tab-button>

    <svg height="50" viewBox="0 0 100 50" width="100" xmlns="http://www.w3.org/2000/svg">
      <path d="M100 0v50H0V0c.543 27.153 22.72 49 50 49S99.457 27.153 99.99 0h.01z" fill="red" fill-rule="evenodd">
      </path>
    </svg>

    <ion-tab-button tab="tab-notifications" class="notifs">
      <ion-icon name="notifications"></ion-icon>
    </ion-tab-button>


  </ion-tab-bar>
</ion-tabs>

tabs.page.scss

最初は何も書いてありませんが、下記のように変更します。


ion-tabs{
	ion-fab {
		margin-bottom: env(safe-area-inset-bottom); /* fix notch ios*/
		ion-fab-button {
			--box-shadow: none;
		}
	}
	ion-tab-bar {
		--border: 0;
		--background: transparent;
		position: absolute;
		bottom: 0;
		left:0; right: 0;
		width: 100%;
		&:after{
			content: " ";
			width: 100%;
			bottom: 0;
			background: var(--ion-color-light);
			height: env(safe-area-inset-bottom);
			position: absolute;
		}
		ion-tab-button {
			--background: var(--ion-color-light);
		}
		ion-tab-button.comments {
			margin-right: 0px;
			border-top-right-radius: 18px;
		}
		ion-tab-button.notifs {
			margin-left: 0px;
			border-top-left-radius: 18px;
		}
		svg {
			width: 72px;
			margin-top: 19px;
			path{
				fill:  var(--ion-color-light);
			}
		}
	}
}

結果

アイコンにメッセージを追加している部分はこのように書いています。

    <ion-tab-button tab="tab-conversations" class="comments">
      <ion-icon name="triangle"></ion-icon>
      <ion-label>Tab 1</ion-label>
      <ion-badge >Hi</ion-badge>
    </ion-tab-button>

元ネタだとちゃんと条件分岐をしていました。

<ion-badge *ngIf="new_message">{{new_message}}</ion-badge>

参考文献

https://forum.ionicframework.com/t/ionic-5-awesome-tab-bar-with-curve/186579

Angular(Ionic)で非同期通信を実装する

1

フロントエンドアプリケーションフレームワークであるAngular(Ionic)で非同期通信を実装する方法をサンプルアプリとして紹介します。動作確認用にPythonで作成したバックエンドコード付きです。

Angularの場合、非同期通信を実装するにはHttpClientModuleをapp.module.tsに追加して、tsファイルで呼び出して使うだけです。AngularはReactと異なりフルスタックなので、この辺の安心感はReactにはるかに勝ると思います。

Angular公式サイトの説明

ソースコードの素性

ソースコードの素性ですが、Ionic(Aunglar)のblankアプリから実装しました。なお、バックエンドのサンプルコードも作成しましたが、こちらはPythonのFlaskを使っています。Flaskでホスティングした http;//127.0.0.1:5000 のURLを叩いてデータを取ってくるアプリです。
GitHubにコードあげたのでそのまま動作確認できると思います。

環境

フロントエンド

Angular CLI: 10.0.5
Ionic CLI : 6.10.0
Node: 12.18.0

バックエンド

Python 3.7.3
Flask 1.1.2
Flask-Cors 3.0.9

コード

app.module.ts

app.module.tsに
import { HttpClientModule } from ‘@angular/common/http’;
を追加します。importするのも忘れずに

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
import { HttpClientModule } from '@angular/common/http';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [BrowserModule,HttpClientModule, IonicModule.forRoot(), AppRoutingModule],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Ionicの場合、moduleファイルが複数あるけど、app.module.tsにだけ書いておけば問題ないです。

home.page.ts

こちらには
import { HttpClient } from ‘@angular/common/http’;
import { DomSanitizer } from ‘@angular/platform-browser’;
の2つを追加します。あとは処理を書くだけです。

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { DomSanitizer } from '@angular/platform-browser';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {
  unsafeImageUrl:any;
  imageUrl:any;

  constructor(
    private http: HttpClient,
    private sanitizer:DomSanitizer)
  {
    this.hello();
  }

  hello(){
    this.http.get('http://127.0.0.1:5000',{responseType:'blob'})
     .subscribe(
       (res:any) => {
         console.log(res);
         this.unsafeImageUrl = URL.createObjectURL(res);
         this.imageUrl = this.sanitizer.bypassSecurityTrustUrl(this.unsafeImageUrl);
       })
  }
}

home.page.html

htmlはを追加するだけです。

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Blank
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Blank</ion-title>
    </ion-toolbar>
  </ion-header>

  <div id="container">
    <img [src]=imageUrl>
    <strong>Ready to create an app?</strong>
    <p>Start with Ionic <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
  </div>
</ion-content>

これでOK.anyを指定してしまっていますので書き方知っていたら教えてください。

バックエンド

バックエンドはこちら

# -*- coding: utf-8 -*-
import io
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from flask import Flask, send_file
from flask_cors import CORS
import json

app = Flask(__name__)
CORS(app)

@app.route('/',methods=["GET","POST"])
def hello():
    image = io.BytesIO()
    x = np.linspace(0, 10)
    y = np.sin(x)
    plt.plot(x, y)
    plt.savefig(image, format='png')
    image.seek(0)
    return send_file(image,
                     attachment_filename="image.png",
                     as_attachment=True)

if __name__ == "__main__":
    app.run(host='127.0.0.1',debug=True)

AngularでWebAPIからblob形式で返された画像データを取得する方法

1

フロントエンドアプリケーションフレームワークであるAngular(Ionic)を使って、非同期通信で画像を読み込む方法をサンプルアプリとして紹介します。動作確認用にPythonで作成したバックエンドコード付きです。

Angularの場合、非同期通信を実装するにはHttpClientModuleをapp.module.tsに追加して、tsファイルで呼び出して使うだけです。画像の場合は、さらにresponseTypeに’blob’形式を指定し、DomSanitizerでサニタイズして読み込みます。HTML側は<img [src]=imageUrl>で行けます。

AngularはReactと異なりフルスタックなので、この辺の安心感があります。

参考にしたサイト

stackOverflow

ソースコード

githubはこちら

解説

app.module.ts

app.module.tsに
import { HttpClientModule } from ‘@angular/common/http’;
を追加します。importするのも忘れずに

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
import { HttpClientModule } from '@angular/common/http';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [BrowserModule,HttpClientModule, IonicModule.forRoot(), AppRoutingModule],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Ionicの場合、moduleファイルが複数あリますが、app.module.tsにだけ書いておけば問題ないです。

home.page.ts

こちらには
import { HttpClient } from ‘@angular/common/http’;
import { DomSanitizer } from ‘@angular/platform-browser’;
の2つを追加します。あとは処理を書くだけです。

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { DomSanitizer } from '@angular/platform-browser';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {
  unsafeImageUrl:any;
  imageUrl:any;

  constructor(
    private http: HttpClient,
    private sanitizer:DomSanitizer)
  {
    this.hello();
  }

  hello(){
    this.http.get('http://127.0.0.1:5000',{responseType:'blob'})
     .subscribe(
       (res:any) => {
         console.log(res);
         this.unsafeImageUrl = URL.createObjectURL(res);
         this.imageUrl = this.sanitizer.bypassSecurityTrustUrl(this.unsafeImageUrl);
       })
  }
}

これでOK。anyを指定してしまっていますので書き方知っていたら教えてください。

home.page.html

htmlはを追加するだけです。

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Blank
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Blank</ion-title>
    </ion-toolbar>
  </ion-header>

  <div id="container">
    <img [src]=imageUrl>
    <strong>Ready to create an app?</strong>
    <p>Start with Ionic <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
  </div>
</ion-content>

バックエンド

バックエンドはこちら

# -*- coding: utf-8 -*-
import io
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from flask import Flask, send_file
from flask_cors import CORS
import json

app = Flask(__name__)
CORS(app)

@app.route('/',methods=["GET","POST"])
def hello():
    image = io.BytesIO()
    x = np.linspace(0, 10)
    y = np.sin(x)
    plt.plot(x, y)
    plt.savefig(image, format='png')
    image.seek(0)
    return send_file(image,
                     attachment_filename="image.png",
                     as_attachment=True)

if __name__ == "__main__":
    app.run(host='127.0.0.1',debug=True)

個人・少人数のシステム開発にはAngularがオススメ

1

Angularとは

 GoogleのドキュメントやGCPで使われているAngularは、Googleが中心開発している JavaScript フレームワークです。AngularJSと混同されていている方もいらっしゃいますが、AngularJSのメリットを生かしたまま互換性のない形で生まれ変わったのがAngularです。Angularは、SPA (Single Page Application)と呼ばれる技術を用いて、従来のWebでは実現できなかった機能を提供する技術です。イメージ的には、従来のWebページよりアプリケーションよりのものを作りやすくなると感じています

SPAの特徴

 最近、従来のWeb技術では考えられなかったようなサイトが増えてきています。たとえば、オフラインでも動作するWebページやGoogleのドキュメントのように同じURLなのに検索ワードによって違う内容が表示されるページなどです。SPA (Single Page Application)ではindex.htmlだけを読み込み、そのあとはDOMの操作をフロントエンドで行って擬似的な遷移を行っています。これによりオフラインでも画面遷移ができたり、検索クエリによって動的にページを書き換えたりといったことができます。

SPAフレームワークにおけるAngularの特徴

 SPAのフレームワークはいくつかあります。Facebookの開発しているReact.jsなどが有名ですが、個人的にはReact.jsよりも断然Angularがおすすめです。Reactが最小限の本体とサードパーティのライブラリで構成されているのに対して、Angularは開発に必要な機能をすべて盛り込んだフルスタックです。開発に必要なデータバインディング、フォームバリデーション、非同期通信、ルーティング、テスタビリティ、セキュリティへの配慮など、一通りの機能がすべてあらかじめ用意されているので、Angular を選択することでコーディングに集中できます。ライブラリが複数あるとどれを使えばいいのか迷いますしライブラリ間の依存関係などを気にする必要が出てくると思いますが、そういった懸念から解放されコーディングに集中できることがAngularのメリットだと考えています。個人的にはAngularは「控えめにいって最高」のフレームワークです。

クロスプラットフォームなのもよい

わたしは個人でシステム開発をしているのですが、iOS用にSwiftで、Android用にKotlinで、そしてWeb用にJavaScriptで開発をしていたらいくら時間があってもたりません。そこで一つのソースコードでさまざまなプラットフォームで動作すればありがたいわけです。そこでクロスプラットフォームの仕組みとして「Ionic 」「Cordova」「NativeScript」「React Native」Electron」「Capacitor」「Xamarin」「Unity」などが候補にあがるわけですが、Angularは「Capacitor」というMacデスクトップアプリ、Windowsデスクトップアプリ、Web、iOS, Androidに出力できるこれまた最高のフレームワークと組み合わせることができます。したがって、Angularは効率的にプロジェクを作成し、それを複数のプラットフォームに同時に出力できるという夢のような生産性を実現するフレームワークです。

AngularJSとの非互換性について

Angularの前身であるAngularJSがあまりにも有名なため、AngularJSの終了から5年弱経っているのにまださまざまなところで混同されています。しかし、AngularはAngularJSで顕在化した問題を解決するべく、内部構造から細部に至るまで再設計され実装されています。

一般的にフルスタックフレームワークだと、開発規模が大きくなるにれバンドルされるファイルサイズが大きくなりパフォーマンス面で悪影響を与えることもあります。AngularJSでも顕在化したその問題を解決するために、AngularではJavaScript VM (Virtual Machine) フレンドリーな実行コードを生成することで実行速度を改善したり、AoT コンパイル(Ahead-of-Time Compilation)と呼ばれるビルド時に表示を高速化するための仕組み、さらに段階的にソースコードをよみこむLazyLoadingと呼ばれる仕組みを利用して高速化する方法も提供されるなどです。最初にAngularで開発するときに訳が分からなくなってしまいがちですが、AngularJSとは互換性がないということを覚えておくといいと思います。半年毎にバージョンが変わるのですが(例えばAngular8が現在のバージョンなら、半年後はAngular9がでる)これらには一部の機能を除いて互換性があります。

まとめと結論

SPAの技術の一つであるAngularについてまとめました。近年普及が急速に進んでいるPWAアプリ作成のフレームワークとして代表的な物で、一つのコードで複数のプラットフォームに出力できるクロスプラットフォーム性について記載しました。

Angularで個人で作ったアプリがあります。OCRという写真から文字を抜き出す技術を使って、ブログなどを早く書くためのツールです。これも一つのコードでAndroid ,Web,iOSに出力できるので大変便利でした。

iOS => https://apps.apple.com/app/id1497498494

Android => https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

Ionic(Angular)のフォルダ構成を完全解説

1

Ionicのフォルダ構成について

 Ionicで作成したプロジェクトは下記のような構造になっています。

directory

 かなり複雑で、最初に見たときは戸惑うかもしれません。実際に触るのはsrc/appフォルダがほとんどになりますが、他のフォルダを含めて解説したいと思います。

ルートフォルダについて

名前説明
e2e/システム全体の動作検証を行うE2Eテストの際に利用します。最初はほとんど触りません
node_modules/node.js上で動くため、package管理ソフトにnpmを使うことになりますが、npm経由でインストールしたライブラリが保存されます
src/実際に開発を行う場所です。9割このフォルダを触ります。
angular.jsonAngularの設定ファイルです。フォルダの出力先などビルドを中心にした設定はここで行います。
package.json実際にプロジェクトで使用するライブラリを指定します
package-lock.jsonpackage.jsonで指定したものが入っているか確認します。エラーの時にたまにいじります

したがって、基本的にはsrcだけをいじると考えておいて差し支えないと思います。

srcフォルダについて

それでは、実際に開発を行うsrcフォルダを見てみます。

srcのなかでも触るのはsrc/appがほとんどになりますが、こちらについても簡単に解説をします。

ファイル/フォルダ説明
src/app実際に触るのはほとんどこのフォルダのなかです。このフォルダが開発対象と考えていただいて問題ないと思います。
src/assets画像や音声、動画などのリソースを配置します。例えばsrc/resourcesというフォルダを自分で作成すると読み込まれないので注意が必要です。
src/environmentsなかには簡単なjsonファイルが本番用と開発用に入っています、環境変数をそれぞれ定義します。Google Analyticsをいれるときなどにいじりました。
src/themecssの拡張であるscssでデザインを規定しますが、そのファイルが入っています。IonicのUIが優れているため、個人的にはあまり触りません。
src/global.scssプロジェクト全体に適用するscssを書く際はこちらを使います。たた、実際はpageごとに用意されるscssをいじることが多いです。
src/index.htmlAnugular(Ionic)でも最初によみこまれるのはIndex.htmlです。
src/main.tsアプリを起動するためのファイルです。index.htmlのあとに読み込まれます。
src/polyfills.tsIE10/
11対応を行うもののようですが、IonicってIEで見れないんですよね。。
src/test.ts単体テストの設定ファイルです

まとめ

基本的に、src/appのなかをいじる、とだけ考えておけばいいと思います。

その他

IonicをAngularで使って、写真から文字情報を抽出して爆速でレポートや記事を作成するアプリを作成しています。無料なのでよかったらどうぞ。

iOS => https://apps.apple.com/app/id1497498494
Android =>  https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

Angularでfirestoreのコレクションからデータ一覧を取得する

1

概要

Angular2以降でfirestoreのコレクションからデータ一覧を取得する方法。

読み込む

コレクションに対して、valueChanges()をsubscribeする。valueChanges()ってなんだ。。

import { Component, ViewChild } from '@angular/core';
import { AuthService } from '../auth/auth.service';
import { AngularFirestore } from '@angular/fire/firestore';

@Component({
  selector: 'app-tab2',
  templateUrl: 'tab2.page.html',
  styleUrls: ['tab2.page.scss']
})
export class Tab2Page {
  authUid:string;

  constructor(
    public auth: AuthService,
    private firestore:AngularFirestore,
  ) {
    this.initialize();
    }

  initialize(){
      this.auth.getAuthUid().subscribe(uid =>{
        console.log(uid,'e');
        this.authUid = uid;
      })

  }

  test(){
    this.firestore.collection('users')
      .doc('data').collection(this.authUid)
      .valueChanges().subscribe(value =>{
        console.log(value);
      },
      error =>{
        console.log('error');
      })
  };
}

オブジェクトの配列で帰ってくる。

書き込み

こちらの方がシンプル

                      this.firestore.collection('users')
                        .doc('data')
                        .collection(this.authUid)
                        .doc(String(new Date().getTime()))
                        //.doc('test')
                        .set({
                          'title':this.originalImgName,
                          'content':data,
                          'filename':this.originalImgName,
                          'lastUpade':String(new Date().getTime()),
                        });

Angular(Ionic)で相方向バインディングを行う最低限のテンプレート

1

概要

Angularでは、相方向バインディングを行うための二つの方法があります。一つはテンプレート駆動型、もう一つはリアクティブフォールによるものです。

こちらの公式

https://angular.jp/guide/forms-overview

によると、

  • リアクティブフォーム はより堅牢です。よりスケーラブルで、再利用しやすく、そしてテストがしやすいです。フォームがアプリケーションの重要なパーツである場合、またはアプリケーションの構築にリアクティブパターンをすでに使用している場合は、リアクティブフォームを使用してください。
  • テンプレート駆動フォーム は、メーリングリストの申し込みフォームなどの単純なフォームをアプリに追加するのに役立ちます。アプリに追加するのは簡単ですが、リアクティブフォームほどスケーラビリティはありません。テンプレートでのみ管理できるとても基本的なフォーム要件とロジックをもつような場合は、テンプレート駆動フォームを使用してください。

ということです。

最低限のリアクティブフォーム

使いたいページのmodule、tsファイル、HTMLの3つに変更を加えます。

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { Tab1p5PageRoutingModule } from './tab1p5-routing.module';
import { Tab1p5Page } from './tab1p5.page';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    IonicModule,
    Tab1p5PageRoutingModule,
    ReactiveFormsModule
  ],
  declarations: [Tab1p5Page]
})
export class Tab1p5PageModule {}
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-tab1p5',
  templateUrl: './tab1p5.page.html',
  styleUrls: ['./tab1p5.page.scss'],
})
export class Tab1p5Page implements OnInit {
  favoriteColorControl:formControl;

  constructor() { }

  ngOnInit() {
    this.favoriteColorControl = new FormControl('');
  }

}
<ion-header>
  <ion-toolbar>
    <ion-title>tab1p5</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  Favorite Color: <input type="text" [formControl]="favoriteColorControl">
{{favoriteColorControl.value}}
</ion-content>

Ionic(Angular)でズーム可能なグラフをchart.jsで作成する

1

概要

Ionic(Angular)でマウスホイールで拡大や移動などが可能なグラフを作成します

方法

標準のchart.jsに加えて、charjs-plugin-zoomを導入します。

通常のchart.jsの導入方法はこちら。

https://np-sys.com/general/438/

これをズーム可能にするためにcharjs-plugin-zoomを導入します。

npm i chartjs-plugin-zoom -s

そしたら、グラフを作成しているpageのtsファイルにて(今回はsrc/app/tab2/tab2.page.tsでした)

import 'chartjs-plugin-zoom';

を上部に追加して、あとはOptionsのなかに

          plugins: {
            zoom: {
              pan: {
                enabled: true,
                mode: 'xy'
              },
              zoom: {
                enabled: true,
                mode: 'xy'
              }
            }
          },

のように指定するだけです。

冗長ですが、コードを貼り付けておきます。貼り付ける場所などの確認にご使用ください。

import { Component, ViewChild } from '@angular/core';
import { Chart } from 'chart.js';
import 'chartjs-plugin-zoom';

@Component({
  selector: 'app-tab2',
  templateUrl: 'tab2.page.html',
  styleUrls: ['tab2.page.scss']
})
export class Tab2Page {
  @ViewChild('myChart') myChart;
  @ViewChild('myChart2') myChart2;
  @ViewChild('myChart3') myChart3;
  bars: any;
  colorArray: any;
  myNum:number;
  data:any=  {
        type: 'line',
        data: {
  				labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'Aug'],
  				datasets: [{
  					label: 'My First dataset',
            backgroundColor: 'rgb(150, 194, 129)', // array should have same number of elements as number of dataset
            borderColor: 'rgb(150, 194, 129)',// array should have same number of elements as number of dataset
  					data: [
  						12,
  						14,
              12,
  						14,
              12,
  						41,
              12,
  						4,
  					],
  					fill: false,
  				}, {
  					label: 'My Second dataset',
  					fill: false,
            backgroundColor: 'rgb(50, 194, 129)', // array should have same number of elements as number of dataset
            borderColor: 'rgb(50, 194, 129)',// array should have same number of elements as number of dataset
  					data: [
              2,
  						4,
              2,
  						4,
              2,
  						4,
              20,
  						142,
  					],
  				}]
  			},
        options: {
          scales: {
            yAxes: [{
              ticks: {
                beginAtZero: true,
                min: 0,
                max: 200
              }
            }]
          },
          responsive:true,
          maintainAspectRatio: false,
          plugins: {
            zoom: {
              pan: {
                enabled: true,
                mode: 'xy'
              },
              zoom: {
                enabled: true,
                mode: 'xy'
              }
            }
          },
          }
      }

      title = 'app';

      columnDefs = [
          {headerName: 'Make', field: 'make' },
          {headerName: 'Model', field: 'model' },
          {headerName: 'Price', field: 'price'},
          {headerName: 'Make', field: 'make2' },
          {headerName: 'Model', field: 'model2' },
          {headerName: 'Price', field: 'price2'},
          {headerName: 'Make', field: 'make3' },
          {headerName: 'Model', field: 'model3' },
          {headerName: 'Price', field: 'price3'},
      ];

      rowData = [
          { make: 'Toyota', model: 'Celica', price: 35000, make2: 'Toyota', model2: 'Celica', price2: 35000, make3: 'Toyota', model3: 'Celica', price3: 35000},
          { make: 'Ford', model: 'Mondeo', price: 32000 },
          { make: 'Porsche', model: 'Boxter', price: 72000 },
          { make: 'Toyota', model: 'Celica', price: 35000, make2: 'Toyota', model2: 'Celica', price2: 35000, make3: 'Toyota', model3: 'Celica', price3: 35000},
          { make: 'Ford', model: 'Mondeo', price: 32000 },
          { make: 'Porsche', model: 'Boxter', price: 72000 },
          { make: 'Toyota', model: 'Celica', price: 35000, make2: 'Toyota', model2: 'Celica', price2: 35000, make3: 'Toyota', model3: 'Celica', price3: 35000},
          { make: 'Ford', model: 'Mondeo', price: 32000 },
          { make: 'Porsche', model: 'Boxter', price: 72000 }
      ];

  constructor(
  ) { }

  ionViewDidEnter() {
    this.createBarChart(this.myChart);
    this.createBarChart(this.myChart2);
    this.createBarChart(this.myChart3);
  }

  onClicked(){
    let v = window.innerWidth;
    alert(v);
  }


  createBarChart(canvasId) {
    this.bars = new Chart(canvasId.nativeElement, this.data);
  }

}

Ionicにag-gridでエクセルライクな表を導入する

1

背景

Webで表をいれたいときってあります。主に3つの方法があると考えております。

・Tableを使う

・FlexBoxを使う

・ライブラリを使う

今回は、ライブラリを使う方法としてAg-gridを導入します。先頭の二つは簡単な表には使用できると思いますが、例えば1万行を超えるようなデータを表にして、Excelのようにセルごとに編集して色分け、、などをするためにはAg-gridを使うのが最適だと思います。

https://www.ag-grid.com/

無料でもできる範囲はかなりひろいです。

環境

$ ionic info
Ionic:
   Ionic CLI : 6.4.1
Utility:
   cordova-res (update available: 0.14.0) : 0.9.0
   native-run (update available: 1.0.0)   : 0.2.9
System:
   NodeJS : v12.10.0
   npm    : 6.14.2
   OS     : macOS Mojave

流れ

ライブラリをnmpでインストールしたあと、プロジェクト全体のレイアウトを管理するglobal.scssで読み込みます。その後、使いたいページのpage.module.tsで読み込んだ後、page.tsで情報を設定して、page.htmlにタグを設置します。

参考文献

https://www.ag-grid.com/angular-grid/

インストール

パッケージのインストールを行います

npm install --save ag-grid-community ag-grid-angular

すると、node_modulesにライブラリが配置されます。

scssの読み込み

Angularではstyles.scss に書き込みますが、ionicではglobal.scssに書き込みます。

@import "../node_modules/ag-grid-community/src/styles/ag-grid.scss";
@import "../node_modules/ag-grid-community/src/styles/ag-theme-alpine/sass/ag-theme-alpine-mixin.scss";

.ag-theme-alpine {
    @include ag-theme-alpine();
}

あとは、これを使いたいページのモジュールで読み込むだけです。ag-gridはモジュールなので、tsファイルで読み込む必要はありません

表を作る

以上で準備完了です。あとは使いたいpageで読み込みます。下記はsrc/app/app.module.ts、src/app/app.component.ts,src/app/app.module.htmlの場合ですが、src/app/tab1/tab1.module.tsなどでも同じです。その場合、src/app/app.module.tsには設定する必要はないです。

それではsrc/app/app.module.tsから編集します。

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { AgGridModule } from 'ag-grid-angular';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AgGridModule.withComponents([])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

Copy

次にsrc/app.component.tsで情報を設定します。

import { Component } from '@angular/core';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss']
})
export class AppComponent {
    title = 'app';

    columnDefs = [
        {headerName: 'Make', field: 'make' },
        {headerName: 'Model', field: 'model' },
        {headerName: 'Price', field: 'price'}
    ];

    rowData = [
        { make: 'Toyota', model: 'Celica', price: 35000 },
        { make: 'Ford', model: 'Mondeo', price: 32000 },
        { make: 'Porsche', model: 'Boxter', price: 72000 }
    ];
}

最後にapp/app.component.htmlファイルでタグを設置します。

<ag-grid-angular
    style="width: 500px; height: 500px;"
    class="ag-theme-alpine"
    [rowData]="rowData"
    [columnDefs]="columnDefs"
    >
</ag-grid-angular>

そうすると、下記のような表が表示されるはずです。

Angularで再読み込み時に404エラーがでる

1

なぜ404エラーとなるのか

AngularをはじめとするSPAでは、URLごとに実態のあるページが存在する一般的なサイトとは異なり、index.htmlをクライアント側で書き換えるため直接特定のアドレスにアクセスするとエラーがでます。

開発環境では出ない理由

開発環境だと開発サーバ側でindex.htmlを読み込んでくれているので問題にならないのですが、デプロイ時には自分で設定することになります。設定内容はファイルが見つからない時はindex.htmlを参照してくれというものですので、Angularではなくサーバ側のapacheやnginxなどの設定ということになります。なのでデプロイ時に顕在化する問題となります。

APacheの場合

.htaccessファイルに下記を指定します。サブドメインを使ってホスティングする場合、サブドメインのルート(サブドメインのindex.htmlのある場所)に下記を設置すればいいです。

<pre class="wp-block-syntaxhighlighter-code">RewriteEngine On
# If an existing asset or directory is requested go to it as it is
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]
# If the requested resource doesn't exist, use index.html
RewriteRule ^ /index.html</pre>

Nginxの場合

こちらのページ(Front Controller Pattern Web Apps)に記載されているtry_filesを使ってindex.htmlに対して下記を設定します

try_files $uri $uri/ /index.html;

Firebaseでホスティングする場合

rootにある設定ファイル(確かfirebase.jsonだったと記憶)を下記のように直せばいいです

"rewrites": [ {
  "source": "**",
  "destination": "/index.html"
} ]

その他の場合

IISやRubyの場合は公式を見ていただくのが良いかと思います。

https://angular.io/guide/deployment#server-configuration

その他

写真から文字情報を抽出して爆速でレポートや記事を作成するアプリを作成しています。無料なのでよかったらどうぞ。

iOS => https://apps.apple.com/app/id1497498494

Android => https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

Ionic(Angular)でtabとサイドバーを備えたプロジェクトを作成する

概要

上記の画像のようなサイドメニューとタブバーを備えたプロジェクトを作成します。

流れ

IonicのデフォルトテンプレートのひとつであるTabプロジェクトにサイドメニューを追加します。

参考文献

https://petercoding.com/ionic/2019/05/05/side-menu-in-ionic4/

https://ionicframework.com/jp/docs/api/menu

準備 デフォルトのプロジェクトを作成する

下記のコマンドを打ってTabバーを備えたプロジェクトを生成します。

% ionic start sidemanu tabs --type=angular

さらにCapacitorを入れますか?(Integrate your new app with Capacitor to target native iOS and Android?) と聞かれたら、スマホアプリにすることを検討指定場合はYESにしておきます。

その後、作成したプロジェクトへ移動します。

$ cd sidemanu

プロジェクトへ移動したあと、下記コマンドで一度起動してみます。

ionic serve

このような画面が立ち上がればOKです。なお、デフォルトがIEなどのブラウザになっているとそもそも表示されないです。ChromeやEdgeでアクセスしてください。

なお、Tabバーのプロジェクト構成は下記となっています。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
    ├── tabs-routing.module.ts
    ├── tabs.module.ts
    ├── tabs.page.html
    ├── tabs.page.scss
    ├── tabs.page.spec.ts
    └── tabs.page.ts

これにサイドメニューを設置して行きます

サイドバーに配置するページを作成しておく

事前にサイドメニューに設置するページを作成しておきます。新しいターミナルを立ち上げてプロジェクトフォルダまで移動したあと、下記コマンドでpageを2つ追加します。

ionic generate page side/mySide1
ionic generate page side/mySide2

プロジェクトは下記のようになります。

.
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.spec.ts
├── app.component.ts
├── app.module.ts
├── explore-container
│   ├── explore-container.component.html
│   ├── explore-container.component.scss
│   ├── explore-container.component.spec.ts
│   ├── explore-container.component.ts
│   └── explore-container.module.ts
├── side
│   ├── my-side1
│   │   ├── my-side1-routing.module.ts
│   │   ├── my-side1.module.ts
│   │   ├── my-side1.page.html
│   │   ├── my-side1.page.scss
│   │   ├── my-side1.page.spec.ts
│   │   └── my-side1.page.ts
│   └── my-side2
│       ├── my-side2-routing.module.ts
│       ├── my-side2.module.ts
│       ├── my-side2.page.html
│       ├── my-side2.page.scss
│       ├── my-side2.page.spec.ts
│       └── my-side2.page.ts
├── tab1
│   ├── tab1-routing.module.ts
│   ├── tab1.module.ts
│   ├── tab1.page.html
│   ├── tab1.page.scss
│   ├── tab1.page.spec.ts
│   └── tab1.page.ts
├── tab2
│   ├── tab2-routing.module.ts
│   ├── tab2.module.ts
│   ├── tab2.page.html
│   ├── tab2.page.scss
│   ├── tab2.page.spec.ts
│   └── tab2.page.ts
├── tab3
│   ├── tab3-routing.module.ts
│   ├── tab3.module.ts
│   ├── tab3.page.html
│   ├── tab3.page.scss
│   ├── tab3.page.spec.ts
│   └── tab3.page.ts
└── tabs
    ├── tabs-routing.module.ts
    ├── tabs.module.ts
    ├── tabs.page.html
    ├── tabs.page.scss
    ├── tabs.page.spec.ts
    └── tabs.page.ts

以上で準備は完了です。あとはこの2つのページへハンバーガメニューからアクセスできるようにします。

サイドメニューを設置

app.component.htmlでサイドメニューを作成し、ion-router-outletに登録する

Ionicは、サイドメニューを簡単に作成するためにion-menuと呼ばれるコンポーネントを提供しています。app.coponent.htmlの中にion-menuタグを使ってサイドメニューを作っておいて、それを<ion-router-outlet>の中でidで紐付けます。ion-router-outletタグはコンポーネントを埋め込むために使用するものですので、これに登録しておくと作成したサイドメニューを他のページから簡単に読み込むことができるようになります。

最初はapp.component.htmlは下記のようになっています。ion-router-outletタグに何も登録がありません。

<ion-app>
  <ion-router-outlet></ion-router-outlet>
</ion-app>

これを下記のようにします。

<ion-app>
    <ion-menu side="start" menuId="first" contentId="my-content">
    '''
    '''
    </ion-menu>
  <ion-router-outdlet id="my-content"></ion-router-outlet>
</ion-app>

こうすることにより、ion-menuタグの中に設定した中身を他のページから読み込むことができるようになります。私の場合、下記のように設定しました。

<ion-app>
    <ion-menu side="start" menuId="first" contentId="content1">
        <ion-header>
          <ion-toolbar>
            <ion-title>Navigate</ion-title>
          </ion-toolbar>
        </ion-header>
        <ion-content>

          <ion-list lines="none">
            <ion-list-header>
              一般情報
            </ion-list-header>

            <ion-menu-toggle autoHide="false">
              <ion-item routerLink="/my-side1" routerLinkActive="active" routerDirection="root" detail="false">
                <ion-icon slot="start" name="person"></ion-icon>
                <ion-label>
                  Privacy Policy
                </ion-label>
              </ion-item>
            </ion-menu-toggle>

            <ion-menu-toggle autoHide="false">
              <ion-item routerLink="/my-side2" routerLinkActive="active" routerDirection="root" detail="false">
                <ion-icon slot="start" name="help"></ion-icon>
                <ion-label>
                  Terms of use
                </ion-label>
              </ion-item>
            </ion-menu-toggle>

          </ion-list>
        </ion-content>
      </ion-menu>
  <ion-router-outdlet id="content1"></ion-router-outlet>
</ion-app>

公式にも実装例が載っています。

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

任意のページからのサイドメニューの読み込み

以上でサイドメニューを設置できましたので、tab1ページから読み込んでみます。tab1.page.htmlはデフォルトではこのようになっていると思います。

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Tab 1
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Tab 1</ion-title>
    </ion-toolbar>
  </ion-header>

  <app-explore-container name="Tab 1 page"></app-explore-container>
</ion-content>

hdearの中で下記のタグで読み込めるようになりますので

      <ion-buttons slot="start">
      <ion-menu-button></ion-menu-button>
      </ion-buttons>

tab1.page.htmlを丸ごとこのように変更します。

<ion-header>
  <ion-toolbar>
      <ion-buttons slot="start">
      <ion-menu-button></ion-menu-button>
      </ion-buttons>
    <ion-title>
      File selection
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Tab 1</ion-title>
    </ion-toolbar>
  </ion-header>

  <app-explore-container name="Tab 1 page"></app-explore-container>
</ion-content>

おまけ

サイドメニューから飛んだ先で戻るボタンを設置できます。

最初はこのようになっていると思いますが、

<ion-header>
  <ion-toolbar>
    <ion-title>mySide2</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>

</ion-content>

下記のようにion-back-buttonを設置します。

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/"></ion-back-button>
    </ion-buttons>
    <ion-title>
      Privacy policy
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>

</ion-content>

そうするとこのように戻るボタンが設置できます。

Angular(Ionic)でグラフを描写する

1

グラフ描写のライブラリ

Ionicでグラフを描写するライブラリにはいくつかあるようですが、安定していてよく使われているchart.jsを使いことにしました。意外に簡単でびっくりしました。

インストール

プロジェクトのルートフォルダに移動して、Terminalで下記コマンドを実行します。

npm install chart.js --save

すると下記のようなメッセージが出てchat.jsがインストールされます。

+ chart.js@2.9.3
added 4 packages from 7 contributors, removed 1 package and audited 1527 packages in 12.531s

node_modulesのなかに格納されるので、これを使いたいページのtsファイルから呼び出すだけで使用できるようになります。

実装する

このような棒グラフを書いてみます。

参考サイト

https://enappd.com/blog/charts-in-ionic-4-apps-and-pwa-part-1/52/

HTMLファイルに要素を追加する

chart.jsはcanvas要素にグラフを描写するので、まずはグラフ要素をHTMLに記載します、

<ion-header>
</ion-header>

<ion-content>
=>  <canvas #myChart></canvas>
</ion-content>

描写する

あとはtsファイルからライブラリをimportしたあとにグラフを描写するだけです。HTMLの要素を取得するので、ViewChildも呼び出します。

import { Component, ViewChild } from '@angular/core';
import { Chart } from 'chart.js';

@Component({
  selector: 'app-admin',
  templateUrl: './admin.page.html',
  styleUrls: ['./admin.page.scss'],
})

export class AdminPage implements OnInit {

  @ViewChild('myChart') myChart;

  bars: any;
  colorArray: any;
  constructor() { }

  ionViewDidEnter() {
    this.createBarChart();
  }

  createBarChart() {
    this.bars = new Chart(this.myChart.nativeElement, {
      type: 'bar',
      data: {
        labels: ['Label1', 'Label2', 'Label3', 'Label4', 'Label5', 'Label6', 'Label7', 'Label8'],
        datasets: [{
          label: 'Viewers in millions',
          data: [25, 30, 50, 60, 92, 75, 100, 67],
          backgroundColor: 'rgb(50, 194, 129)', // array should have same number of elements as number of dataset
          borderColor: 'rgb(50, 194, 129)',// array should have same number of elements as number of dataset
          borderWidth: 1
        }]
      },
      options: {
        scales: {
          yAxes: [{
            ticks: {
              beginAtZero: true
            }
          }]
        }
      }
    });
  }
}

Agular(ionic)で多言語化対応を行う

1

環境

Ionic:

 Ionic CLI: 6.4.1 (/Users/myName/.nodebrew/node/v12.10.0/lib/node_modules/@ionic/cli)
   Ionic Framework  : @ionic/angular 5.1.1
   @angular-devkit/build-angular : 0.901.6
   @angular-devkit/schematics: 9.1.6
   @angular/cli   : 9.1.6
   @ionic/angular-toolkit : 2.2.0

Utility:
   cordova-res (update available: 0.14.0) : 0.9.0
   native-run (update available: 1.0.0)   : 0.2.9

System:
   NodeJS : v12.10.0 (/Users/masaya/.nodebrew/node/v12.10.0/bin/node)
   npm    : 6.14.2
   OS     : macOS Mojave



参考のサイト

公式ドキュメントはこちら

https://github.com/ngx-translate/core

パッケージのインストール

npm install @ngx-translate/core @ngx-translate/http-loader --save

package.json

  "dependencies": {
    "@ngx-translate/core": "^12.1.2",
    "@ngx-translate/http-loader": "^4.0.0",

実装

app.module.ts

app.module.tsにて、TranslateModuleをHttpClientModule, HttpClientと一緒にインポートします.

import {HttpClientModule, HttpClient} from '@angular/common/http';
import {TranslateModule} from '@ngx-translate/core';
・
  imports: [
    HttpClientModule,    
    TranslateModule.forRoot(),
  ],

使いたい場所のモジュール

使いたい場所のモジュールで

import {HttpClientModule, HttpClient} from '@angular/common/http';
import {TranslateModule, TranslateLoader} from '@ngx-translate/core';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';

export function createTranslateLoader(http: HttpClient) {
    return new TranslateHttpLoader(http, '../../assets/i18n/', '.json');
}
@NgModule({
    imports: [
      TranslateModule.forChild({
        loader: {
           provide: TranslateLoader,
           useFactory: (createTranslateLoader),
           deps: [HttpClient]
         }
      }),
    ],

使いたい場所のtsファイル

import {TranslateService} from '@ngx-translate/core';
 constructor(
    private translate: TranslateService,
  ) {
    this.translate.setDefaultLang('en');
    this.translate.use('en');
}

あとHTMLに

<p>{{ 'HELLO' | translate }}</p>

とかく

assetに情報を配置

最後に

/assets/i18n/

を作成して、jsonファイルをおけばいいです。

{
  "HELLO": "こんにちはEnglish"
}

複数の場合はこのように書いてください

{
  "Main": "メイン",
  "File selection": "ファイルの選択",
  "1. Choose a file": "1. ファイルの選択",
  "Pick a file from your storage": "ストレージから選択",
  "Or take a new picture": "新しく写真を撮る",
  "2. Crop your image": "2. 写真の切り抜き",
  "select the area you want to analyze from the photo": "解析したいエリアを選択してください",
  "No ads 3 times if you watch a short video": "広告が3回表示されなくなります!",
  "Preparing for the analysis. Since this app uses service that need to be payed(from not you but me), an ad will be shown to you.":"安全な方法でアップロードします。 このアプリは有料のAPIを使用しているため、広告が表示されます。",
  "Analyzing.":"分析しています。"
}

最後のブロックの後にカンマを入れるとエラーになりますので注意.

app.component.htmsも多言語化したい場合

app.module.tsを下記のようにします.

+import {HttpClientModule, HttpClient} from '@angular/common/http';
+import {TranslateModule, TranslateLoader} from '@ngx-translate/core';
+import {TranslateHttpLoader} from '@ngx-translate/http-loader';

+export function createTranslateLoader(http: HttpClient) {
+    return new TranslateHttpLoader(http, '../assets/i18n/', '.json');
+}

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
+    TranslateModule.forRoot({
+      loader: {
+         provide: TranslateLoader,
+         useFactory: (createTranslateLoader),
+         deps: [HttpClient]
+       }
+    }),
  ],
  providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
  bootstrap: [AppComponent],
})
export class AppModule {}

app.component.ts

+import {TranslateService} from '@ngx-translate/core';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss'],
})
export class AppComponent {

  constructor(
+    private translate: TranslateService,
  ) {
+      this.translate.setDefaultLang('en');
+      this.translate.use('en');
  }
}

Ionic(Angular)のライフイベントについて

1

ライフイベント、ライフサイクルとは

ライフイベント、ライフサイクルとは、ページを読み込み、表示し、その後ユーザーが離脱する一連の流れの中で特定のタイミングで発火するDOMイベントのことです。Ionicでは、Ionic独自のライフイベントに加えて、Angularのライフイベントも利用することができます。

これにより、ページが読み込まれたらこれをする、ページに変化があったらこれをするといったことを実装できます。

Angularのライフイベントについて

 それまでまずはAngularのライフイベントからです。Ionicでは、Ionic独自のライフイベントに加えて、Angularのライフイベントも利用することができます。個人的にはAngularのイベントを使う頻度の方が高いです。

こちらの公式ドキュメントに細かく記載されておりますので、ここではよく使うライフイベントについて転載します。

https://angular.jp/guide/lifecycle-hooks

イベントフック(これを関数のように書いて呼び出します)概要
ngOnInit()ページやコンポートネントが最初に完全に読み込んだら実行されるものと理解しています。イメージではJavaScriptのonDOMcontentLOaded()です。その後、 ディレクティブ/コンポーネントを初期化します。ページ読み込みの際に1度だけ呼び出されます。
最初 の nOnChanges() の後に 一度 呼び出されます
ngOnChanges()Angular がデータバインドされた入力プロパティを(再)設定するときに応答します。 このメソッドは、現在および以前のプロパティ値の SimpleChanges オブジェクトを受け取ります。
ngOnInit() の前に呼び出され、データバインドされた入力プロパティが変更されるたびに呼び出されます。
ngOnDestroy()Angularがディレクティブ/コンポーネントを破棄する直前に、クリーンアップします。 メモリリークを回避するためにObservableの購読を解除し、イベントハンドラをデタッチしましょう。
Angularがディレクティブ/コンポーネントを破棄する 直前 に呼び出されます。

Angular場合、ライフサイクルはコンポーネントの生成や破棄がトリガーになります。そのため、最初の1回しかイベントを実行したくないときなどに利用します。

なお、constructorとngInitを同じように考えがちですが、前者はライフイベントではありませんので、コンポーネントが表示されるまで待ってくれません。そのため、constructorを実行したのになにも反映されないということがあります。constructorではページ操作を行うような動作は入れない方が良いです。

Ionicのライフイベント

 次にIonicのライフイベントです。

イベントフック(これを関数のように書いて呼び出します)概要
ionViewWillEnter()ページが表示されるアニメーションが始まるとき
ionViewDidEnter()ページが表示されるアニメーションが終了したとき
ionViewWilLeave()ページから離脱・遷移するアニメーションが始まるとき
ionViewDidLeave()離脱するアニメーションが終了したとき

Willのタイミングでデータの描画を行うのはよくないです。Will を使用する場合はデータの取得などの準備、Didを使用するのはデータを表示するなどのタイミングがよさそうです。

 Angularの場合、先に述べたようにコンポーネントがトリガーとなっているのに対して、lonicのライフライクルトリガーはユーザーにとっての表示です。なのでページ遷移して戻ってきたときにも発生するなどでUIの改善などの観点では非常に使いやすくなっています。

ライフイベントの実装方法

Ionicの場合、ライフサイクルイベントを関数のように追記するだけですが、Angularのライフサイクルイベントは先に型を定義する必要があります。

その他

IonicをAngularで使って、写真から文字情報を抽出して爆速でレポートや記事を作成するアプリを作成しています。無料なのでよかったらどうぞ。

iOS => https://apps.apple.com/app/id1497498494
Android => https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr

Ionicの忘備録

Observableなオブジェクトの作成

import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
・
・
  constructor(
    private client:HttpClient
  ) { }  
・
・
devTestObservable(myStr):Observable<any> {
    return new Observable(observer => {
      setTimeout(() => {
        observer.next(myStr+'myObservable');
      }, 1000);
    })
  }

  devTestPromise() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('myPromise');
      }, 1000)
    })
  }

  devTestHttp():Observable<any> {
    return new Observable(observer => {
        this.client.get('/assets/data/test.json').subscribe(e =>{
          observer.next(e);
        })
    })
  }

Copy

  
import { DataService } from '../tabs/data.service';
import { HttpClient } from '@angular/common/http';
・
・
  constructor(
    private dataService:DataService,
    private client:HttpClient,
  ) {
      this.myStr = 'first ';
  }
・
・
async onButtonClick(){

    this.dataService.devTestHttp().subscribe(e =>{
      console.log(e,'Hi');
      this.myStr = e.authAdmin.linkLoginButton+'Hi';
    })

    this.dataService.devTestObservable().subscribe(e =>{
      console.log(e);
    })

    this.dataService.devTestPromise().then(e =>{
      console.log(e);
    })

  }

Copy

input

<ion-item lines=’full’><ion-label position=’floating’>らべる</ion-label><ion-input type=’text’></ion-input></ion-item>

inputには、最初にplaceholderが表示されていて、フォーカスをあてるとplaceholderのラベルが左上に移動するものが一番いいです。

こんな感じになります。

フォーカス前

フォーカス後

ボタン

横いっぱいに広がったよく見るボタンが表示されます。クリック時の関数は(click)=で指定します。

<div><ion-button expand=’block’ (click)=”someFunc()”>Button</ion-button></div>

新しい部品をつくる

ionic g

とだけ入力すれば、下記の選択肢を表示してくれます。

Angularにおけるモジュール解説

1

モジュールとは

Anularは、コードをモジュールという単位で機能ごとに分割することで管理します。電気自動車が部品を組み立てることで比較的簡単に組み立てられるように、機能ごとにモジュールへ分割することでプログラムを作成しやすくなります。代表的なものとして、ルーティングや HTTP 通信などの機能を持つものなどがあります。

CommonModule

CommonModuleはNgIf や NgFor などの基本的なディレクティブやパイプを提供します。両者ともAngularを代表する機能ですが、Ngifは変数の条件によって(例えばログインしているかしていないかで)表示を変えるときに使用しますし、NgForはリスト構造になっているものに対してイテレートすることで劇的にソースコードを書く手間を低減できます。Angularを代表する機能であるモジュールのため直接読み込む必要はなく、BrowserModule やWorkerAppModule などの各プラットフォームごとのモジュールがエクスポートしているので、これらのモジュールを読み込んでいれば同時に利用可能になります。

Forms Module

Forms Moduleは、Template Driven なフォームを実装するのに必要となるディレクティブやプロバイダを提供します。Template Driven はフォームにユーザーが入力した情報によって変数側のデータを書き換える方法で、これによりJavaScriptからクラスやIDを指定してgetElementbyID()などの手間を省くことができます。Forms Module は、@angular/forms からインポートすることで利用できます。

src/app/app.module.ts

import { NgModule }      from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { FormsModule }   from '@angular/forms';import { AppComponent }  from './app.component'; @NgModule({imports: [BrowserModule,FormsModule],declarations: [AppComponent,],providers: [],bootstrap: [ AppComponent ]})export class AppModule { }

Reactive FormsModule

ReactiveForms Module は、アプリケーションに Reactive Form を実装するために必要なディレクティブやプロバイダを提供します。Forms Moduleがユーザー側の入力でバックエンド側の変数を書き換える方法なのに対して、このモジュールはバックエンド側からフロントエンドを書き換える方法です。ReactiveForms Module は Forms Module と同様に、@angular/forms からインポートして利用します。

HttpClientModule

HttpClientModule は、HTTP リクエストや、レスポンスの処理を実装するのに必要なプロバイダを提供します。Pythonなどとは異なり非同期通信だるJavaScript(そしてSupersetであるTypeScript)では、Ajax通信をよく使いますが、Ajax通信をAngularで実装する際はこのモジュールを使用します。AjaxHttpClientModule は @angular/common/http からインポートすることで利用可能になります。

JsonpModule

JsonpModule は、JSONP を用いた HTTP 通信を実装するのに必要なプロバイダを提供します。JsonpModule は HttpClientModule と同様、@angular/http からインポートすることで、利用可能になります。個人的にはあまり使用したことはありません。

Router Module

AngularはSPAですので、実態は一枚のHTMLのDOMを書き換えてページ遷移を実現します。その際のURL変更に関する機能を担当するモジュールです。RouterModule は @angular/router からインポートすることで利用します。

BrowserModule 

BrowserModule は、DOM イベント(たとえばheaderの読み込みが完了したかなど(https://phpjavascriptroom.com/?t=js&p=event))やキーボードイベント・タッチイベントなど、主にブラウザアプリケーションの作成に必要な機能を提供します。これもWebからきている人には理解しやすい概念だと思います。なお、BrowserModule のなかでCommonModule もエクスポートしているため、BrowserModule をインポートするだけでCommonModule も読み込まれます。@angular/platform-browser からインポートして利用しますが、個人的には何も考えずにインポートするモジュールです。

ServerModule

ServerModule は、主にサーバーサイドレンダリングを行うアプリケーションを作成するために必要な機能を提供します。公式を見ると解説が省略されすぎているのですが。。

https://angular.io/api/platform-server/ServerModule

前述の BrowserModule もエクスポートしているので、BrowserModule で使える機能も利用できます。@angular/platform-server からインポートします。サーバ側はPythonで書くことが多いので、あまり個人的に使用したことはありません。

WorkerAppModule

これも個人的にはほとんど使ったことがないモジュールで、公式によると、Angular10から廃止されることが決まっておりますので使用しないほうがいいでしょう。

https://angular.jp/api/platform-webworker/WorkerAppModule

まとめと結論

Anularは、モジュールという単位で特定の機能を担うコードを分割し、使用する場合はImportしてすぐに使用できるようにしています。個人的には、

  • 存在をしっていればいいもの
    BrowserModule
  • 使う中でわかってくるもの
    Forms Module、Reactive FormsModule
  • 勉強して使えるようにならないといけないもの
    HttpClientModule

というイメージです。

写真から文字情報を抽出して爆速でレポートや記事を作成するアプリを作成しています。無料なのでよかったらどうぞ。

iOS => https://apps.apple.com/app/id1497498494
Android =>  https://play.google.com/store/apps/details?id=com.rainbowsv2.ocr