概要
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で参照して
下記のようにフォーマットを指定すれば良い。
<div>{{testDate|date:"yyyy年MM月dd日 HH時mm分"}}</div>
2. TypeScriptでフォーマットを指定する
typeScriptで指定する場合は二つの方法がある
2.1. app.module.tsのprovidersにDatePipeを追加後コンポーネントでimportして使用する
こちらの場合はapp.module.tsと使用するコンポーネント二つ修正する必要がある
import { DatePipe } from '@angular/common';
@NgModule({
  // ... 省略
  providers: [DatePipe],
  // ... 省略
})
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. 各コンポーネントの@Componentにprovidersを追加してDatePipeを含める
こちらの場合は使用するコンポーネントのみ修正する必要がある
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で作成したWebサイトをGAEに乗せたい
こちら参考にさせていただきました。
Angular 製のアプリケーションを Google App Engine にデプロイする
今回作成したWebアプリケーションはビルド時にng build --output-path=./server/staticを指定し、かつNode.jsを使用したため、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をインポートする
// インポートに追加
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
// インポートに追加
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>を作る
<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-XXの項目を当てた時のplaceholderが上に移動するのを止めたい
mat-labelを使用している場合はmat-form-fieldタグ内にfloatLabel="never"を追加する
もしくはmat-labelを使用せずにplaceholderを使用する
<!-- 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 は罠が多すぎる
// インポートに追加
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 {
  // ... 省略
}
<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>
  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を実行
 
![]()  | 
|---|
回避方法
pickerが開く時はopened、閉じる時はclosedイベントが発生するのでそちらのイベントから手動でフラグを作成した
picker1に対して追加でmarkAsTouchedを発行しているのは黄枠の部分もエラー表示にしたいため

<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>
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
<form #testForm="ngForm" appIdentityRevealed (keydown.enter)="handleEnterKeyPress($event)">
// ... 省略
</form>
  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を作成しない場合)
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を使用している各自コンポーネントに追加した場合は下記のような表示になる

2. app.module.tsに追加する場合(CustomDateAdapterを作成する場合)
上の方法だとDatePickerを開いた場合の年月表記が逆になっていたりするので、余裕があるならCustomDateAdapterを作成した方が良い
date inputの救世主!? Angular Materialのdatepickerを使ってみた
ファイル選択のボタンにデフォルトのボタンではなくAngular Materialのボタンを使用したい
下記の記事を参考にさせていただきました。
Mat-Chipの縦の高さを変更可能にしたい
mat-chip内に自動生成されるmat-standard-chipで高さが指定されている模様。
mat-chipに対して以下のcssを指定することで高さが変動できるようになる。
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でファイル選択ボタン


