16
32

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Angular(とAngular Material)を使った際にはまった事

Posted at

概要

Angular7でWebアプリケーションを開発した際に色々とはまったことが数多く出てきたので戒めの今後のために
記事にまとめておくことにします。
今回の開発ではAngular Materialも使用しましたのでそちらで発生した問題についてもまとめて記載しておきたいと思います。

はまった事と解決方法一覧

ネットワークの外部に出られない

1. 詳細

今回の開発で一番苦労したと思うところがこれでした。
angular-in-memory-web-apiを使用している場合、そのままだと外部のネットワークに出ることができないということを知らずに数日エラーと戦っていました。
AngularでHTTPアクセスしようとしたら「statusText: Not Found」

下記のようにURLアクセスと共存させる方法もありますが、基本的には開発中に使用するのであれば、jsonファイルを作成してそのファイルに対してHttpClientでアクセスした方が内部のテスト環境と外部の環境の切り替えがしやすいと思います。
Angular2 angular-in-memory-web-api で モック と urlアクセスを共存させる方法

内部環境では一旦下記のようにやっていました。本来は環境の切り替えのみで内部と外部に対する接続を切り替えられるようにしなければならないのですが、調査時間がなかったためできていません。
調べ次第ここに追記します。

  constructor(
    private http: HttpClient,
  ) { }

// ... 省略

const httpOptions = {
  headers: new HttpHeaders({
    'Content-Type': 'json',
  })
};
return this.http.get<ResponseClass>('jsonfile.json', httpOptions);

ページ遷移の場合でページを読み込んだ後に白飛びしないようにする

AppRoutingModuleを使用している場合にhref=xxxではなくnavigateByUrl('/xxx_link')を使用する
AppRoutingModuleを使用する場合で同一のサイト内を移動する際はnavigateByUrlを使用した方が良いと思われる

日付をフォーマットして表示したい

こちらの記事がわかりやすかったので参考にさせていただきました。
Angular 日付のフォーマット

DatePipeについての説明とフォーマットオプション一覧
Angular:DatePipe

1. htmlでフォーマットを指定する

htmlでフォーマットする場合はapp.module.ts等に追加する必要はなく、Date型の変数をhtmlで参照して
下記のようにフォーマットを指定すれば良い。

module.component.html
<div>{{testDate|date:"yyyy年MM月dd日 HH時mm分"}}</div>

2. TypeScriptでフォーマットを指定する

typeScriptで指定する場合は二つの方法がある

2.1. app.module.tsprovidersにDatePipeを追加後コンポーネントでimportして使用する

こちらの場合はapp.module.tsと使用するコンポーネント二つ修正する必要がある

app.module.ts
import { DatePipe } from '@angular/common';

@NgModule({
  // ... 省略
  providers: [DatePipe],
  // ... 省略
})
module.component.ts
import { DatePipe } from '@angular/common';

@Component({
  selector: 'module-component',
  templateUrl: './module-component.html',
  styleUrls: ['./module-component.scss']
})
export class ModuleComponent implements OnInit {
  date1 = new Date();
  date2: string;

  constructor(
    private datepipe: DatePipe,
  ) { }

  ngOnInit(): void {
    this.date2 = this.datepipe.transform(this.date1, 'yyyy年M月d日 HH時mm分ss秒');
  }
}

2.2. 各コンポーネントの@Componentprovidersを追加してDatePipeを含める

こちらの場合は使用するコンポーネントのみ修正する必要がある

module.component.ts
import { DatePipe } from '@angular/common';

@Component({
  selector: 'module-component',
  templateUrl: './module-component.html',
  styleUrls: ['./module-component.scss'],
  providers: [DatePipe]
})
export class ModuleComponent implements OnInit {
  date1 = new Date();
  date2: string;

  constructor(
    private datepipe: DatePipe,
  ) { }

  ngOnInit(): void {
    this.date2 = this.datepipe.transform(this.date1, 'yyyy年M月d日 HH時mm分ss秒');
  }
}

2.3 結局どちらにDatePipeのプロバイダーを設定すれば良い?

頻繁にロードされるコンポーネントの場合はapp.module.ts(ルートモジュール)に追加した方が良いが、多くなるとロードに時間がかかる
遅延ロードが有効な場面かどうかを確認して、ルートモジュールもしくは各コンポーネントに追加するかを選択する

Angular:プロバイダー

Angularで作成したWebサイトをGAEに乗せたい

こちら参考にさせていただきました。
Angular 製のアプリケーションを Google App Engine にデプロイする

今回作成したWebアプリケーションはビルド時にng build --output-path=./server/staticを指定し、かつNode.jsを使用したため、app.yamlは以下のようになりました。

./server/app.yaml
runtime: nodejs8

handlers:
- url: /(.*\.(css|gif|png|jpg|ico|js|html|json))
  static_files: static/\1
  upload: static/(.*\.(css|gif|png|jpg|ico|js|html|json))
- url: /(.*)
  static_files: static/index.html
  upload: static/index.html

Mat-XXに対してngModelが設定できない

app.module.tsに対してFormsModuleをインポートする

app.module.ts
// インポートに追加
import { FormsModule } from '@angular/forms';

@NgModule({
  imports: [
    // ... 省略
    FormsModule,
    // ... 省略
  ],
})
export class AppModule {
  // ... 省略
}

Angular 4 Error: Can’t Bind to ngModel since it isn’t a known property of select

mat-XXでアニメーションが発生しない

app.module.tsに対してBrowserAnimationsModuleもしくはNoopAnimationsModuleをインポートする
Angular:Angularアニメーション・イントロダクション
Angular:Angular Material Getting started

app.module.ts
// インポートに追加
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
// アニメーションを無効化したい場合はこちらをインポート
import { NoopAnimationsModule } from '@angular/platform-browser/animations';

@NgModule({
  imports: [
    // どちらか一方をimportsに含める
    BrowserAnimationsModule,
    NoopAnimationsModule,
    // ... 省略
  ],
})
export class AppModule {
  // ... 省略
}

Ripple effect is not displaying in button in angular app - Angular-Material

mat-selectに初期値未定の項目を作りたい

[value]未定の<mat-option>を作る

module.component.html
<mat-form-field floatLabel="never">
  <mat-label>内容を選択</mat-label>
  <mat-select name="selectItem" [(ngModel)]="selectItem">
    <mat-option>未選択</mat-option>
    <mat-option *ngFor="let slt of selectModels" [value]="slt.id">
      {{slt.description}}
    </mat-option>
  </mat-select>
</mat-form-field>

上のように設定した場合、mat-selectを選択すると「未選択」項目が表示される

mat-select_default_01.png

「未選択」項目を選択した場合、項目が表示されずに「内容を選択」に戻る

mat-select_default_02.png

mat-XXの項目を当てた時のplaceholderが上に移動するのを止めたい

mat-labelを使用している場合はmat-form-fieldタグ内にfloatLabel="never"を追加する
もしくはmat-labelを使用せずにplaceholderを使用する

module.component.html
<!-- mat-selectの場合はfloatLabel="never" -->
<mat-form-field floatLabel="never">
  <mat-label>内容を選択</mat-label>
  <mat-select name="selectItem" [(ngModel)]="selectItem">
    <mat-option>未選択</mat-option>
    <mat-option *ngFor="let slt of selectModels" [value]="slt.id">
      {{slt.description}}
    </mat-option>
  </mat-select>
</mat-form-field>

<!-- inputタグはこちらも使用可能 -->
<div><input matInput placeholder="内容を入力"></div>

DatePickerの値をDateもしくはStringで取りたい・入れたい

こちら参考にさせていただきました。
JavaScript の Date は罠が多すぎる

app.module.ts
// インポートに追加
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatInputModule } from '@angular/material/input';
import { MatNativeDateModule } from '@angular/material/core';

@NgModule({
  imports: [
    // ... 省略
    MatDatepickerModule,
    MatInputModule,
    MatNativeDateModule,
    // ... 省略
  ],
})
export class AppModule {
  // ... 省略
}
module.component.html
<div>{{selectDate2}}</div>
<mat-form-field floatLabel="never">
  <input matInput name="picker1" [matDatepicker]="picker1" placeholder="日付を選択" [(ngModel)]="selectDate"
    (dateChange)="onDatePickerChange($event)">
  <mat-datepicker-toggle matSuffix [for]="picker1"></mat-datepicker-toggle>
  <mat-datepicker #picker1></mat-datepicker>
</mat-form-field>
module.component.ts
  ngOnInit(): void {
    // 文字列を日付に変換
    const date = '20181231';
    const dateStr = date.substr(0, 4) + '/' + date.substr(4, 2) + '/' + date.substr(6, 2);
    const time = ' 00:00:00';
    this.selectDate = new Date(dateStr + time);
  }

  // 設定された日付を取得して文字列に変換
  public onDatePickerChange(event: any): void {
    // 取得したプロパティをそのまま使用する場合、未設定時にnullになるため一度インスタンスを生成する
    const dateStr = new Date(this.selectDate);
    const yyyy = ('0000' + dateStr.getFullYear()).slice(-4);
    // 月は0〜11で取得されるので+1 足す
    const mm = ('00' + (dateStr.getMonth() + 1)).slice(-2);
    const dd = ('00' + dateStr.getDate()).slice(-2);

    this.selectDate2 = yyyy + mm + dd;
  }

DatePickerに対して日付ピッカーを開いた後にValidationを入れたい

製造時、DatePickerを使用した際に下記のような動作を入れたくなった

  • 日付の直接入力は不可
  • アイコンクリックして日付入力(または未入力でキャンセル)時にValidationを実行
DatePicker_Validation_01.png

回避方法

pickerが開く時はopened、閉じる時はclosedイベントが発生するのでそちらのイベントから手動でフラグを作成した
picker1に対して追加でmarkAsTouchedを発行しているのは黄枠の部分もエラー表示にしたいため
DatePicker_Validation_02.png

module.component.html
<form #testForm="ngForm" appIdentityRevealed>
// ... 省略
  <mat-form-field floatLabel="never" hideRequiredMarker>
    <input matInput name="picker1" [matDatepicker]="picker1" placeholder="日付を選択" [(ngModel)]="selectDate"
      (dateChange)="onDatePickerChange($event)" readonly required #selectDate1="ngModel">
    <mat-datepicker-toggle matSuffix [for]="picker1"></mat-datepicker-toggle>
    <mat-datepicker #picker1 name="date1" (opened)="streamOpened()" (closed)="streamClosed(testForm)"></mat-datepicker>
    <mat-hint class="alert alert-danger" *ngIf="pickerClosed && selectDate1?.errors?.required" class="icon-warn">
      日付は必須入力です。
    </mat-hint>
  </mat-form-field>
// ... 省略
</form>
module.component.ts
import { NgForm } from '@angular/forms';
// ... 省略
export class ModuleComponent implements OnInit {
  pickerClosed = false;
// ... 省略
  public streamOpened() {
    this.pickerClosed = false;
  }

  public streamClosed(form: NgForm) {
    this.pickerClosed = true;
    form.controls.picker1.markAsTouched({ onlySelf: true });
  }
}

Enterキーを押した時にFormの送信を防ぐ

テキストエリアのみEnterキーを有効(改行可)にして他のFormではEnterキーでのForm送信を無効化したい
Angular 2 prevent enter from submitting in template-driven form

module.component.html
<form #testForm="ngForm" appIdentityRevealed (keydown.enter)="handleEnterKeyPress($event)">
// ... 省略
</form>
module.component.ts
  handleEnterKeyPress(event) {
    const tagName = event.target.tagName.toLowerCase();
    if (tagName !== 'textarea') {
      return false;
    }
  }

DatePickerのロケーションを変更したい

普通にDatePickerを作るとロケーションが(確か)en-USなのでja-JPに変更したい

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

下記2つのパッケージをインストール

npm install moment
npm install @angular/material-moment-adapter

1. app.module.tsに追加しない場合(CustomDateAdapterを作成しない場合)

module.component.ts
import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
import { MAT_MOMENT_DATE_FORMATS, MomentDateAdapter } from '@angular/material-moment-adapter';

@Component({
  // ... 省略
  providers: [
    { provide: MAT_DATE_LOCALE, useValue: 'ja-JP' },
    { provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] },
    { provide: MAT_DATE_FORMATS, useValue: MAT_MOMENT_DATE_FORMATS },
  ],
})

DatePickerを使用している各自コンポーネントに追加した場合は下記のような表示になる
DatePicker_change_location_01.png

2. app.module.tsに追加する場合(CustomDateAdapterを作成する場合)

上の方法だとDatePickerを開いた場合の年月表記が逆になっていたりするので、余裕があるならCustomDateAdapterを作成した方が良い
date inputの救世主!? Angular Materialのdatepickerを使ってみた

ファイル選択のボタンにデフォルトのボタンではなくAngular Materialのボタンを使用したい

下記の記事を参考にさせていただきました。

Angular Materialでファイル選択ボタン

Mat-Chipの縦の高さを変更可能にしたい

mat-chip内に自動生成されるmat-standard-chipで高さが指定されている模様。
mat-chipに対して以下のcssを指定することで高さが変動できるようになる。

style.scss
mat-chip {
  // mat-standard-chipの高さ固定を上書き
  height: auto;
}

最後に

実装に迷ったところが出てきたら随時追記します。

参考

AngularでHTTPアクセスしようとしたら「statusText: Not Found」
Angular2 angular-in-memory-web-api で モック と urlアクセスを共存させる方法
Angular 日付のフォーマット
Angular:DatePipe
Angular:プロバイダー
Angular 製のアプリケーションを Google App Engine にデプロイする
Angular 4 Error: Can’t Bind to ngModel since it isn’t a known property of select
Angular:Angularアニメーション・イントロダクション
Angular:Angular Material Getting started
Ripple effect is not displaying in button in angular app - Angular-Material
JavaScript の Date は罠が多すぎる
Angular 2 prevent enter from submitting in template-driven form
date inputの救世主!? Angular Materialのdatepickerを使ってみた
Angular Materialでファイル選択ボタン

16
32
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
32

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?