vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

Angularで引っかかったあれこれ

はじめに

相変わらず上の続きなのですが、Angular周りで細々したことに引っかかりまくったため、ここにまとめることとします。

静的なファイルの読み込み

たとえばヘッダーのロゴ画像など、静的にファイルを読み込みたい場合。

普通にHTMLで書くときと同じように、HTMLファイルと同じ階層に画像ファイルを置いても読み込むことはできません。

angular-cliを使っている場合、ファイルの置き場所は.angular-cli.jsonの中の、app > assets の中で指定されます。

.angular-cli.json

〜省略〜
  "apps": [
    {
〜省略〜
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.app.json",
      "testTsconfig": "tsconfig.spec.json",
      "prefix": "app",
      "styles": [
        "styles.css"
      ],
      "scripts": [],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
  ],
  〜省略〜

デフォルトでは favicon.ico と src > assets が指定されており、
例えば src > assets > img > image01.jpg となるようフォルダと画像ファイルを追加した場合、
「< img src=“assets/img/image01.jpg”>」で表示できるようになります。

動的にページタイトルを設定する

ルーティングは以前やりましたが、ページが切り替わったらタイトルも変更したいところ。

ページタイトルを切り替えるには、対象のページのクラスにDIで「Title」クラスを注入し、
「setTitle()」を使って設定します。

main-page.component.ts

import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';

@Component({
  selector: 'app-main-page',
  templateUrl: './main-page.component.html',
  styleUrls: ['./main-page.component.css']
})
export class MainPageComponent implements OnInit {

  constructor(private titleService: Title) {}
  ngOnInit() {
    this.titleService.setTitle('メインページ');
  }

}

IE対応

気になってはいたのです。。。
IEでちゃんと開けるのかということに。

とはいえ、下記を見ればIE9以降であればサポートはされているようなので、
諸事情によりサポート切れのブラウザで表示する必要が。。。みたいな環境でなければ問題ないはずw

ところが、IE11で開くとなにやらエラーが発生してページが正しく動作しませんorz

IEに対応するためには、プロジェクト > src にあるpolyfills.tsの下記の部分をアンコメントする必要があります。

polyfills.ts

/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';

ちなみにここで登場するcore-jsは、ブラウザ間の差異を吸収してくれるもののようです。
(この辺もちゃんと調べたいと思いつつ何もできてないマンですはい)

デフォルトでこれがオンになってない理由は、余計なファイルを含めたくない、ということなのでしょうか。

Componentをどう分けるか

これまで1ページの中の要素でも、ひとかたまりごとにComponentに分けて作っていました。

ただ、この辺りのお話を見ていると、そうではなく複数の箇所から共通で使う部分だけを切り分ける、
とした方が良いように思えてきました。

今から思えば、たしかにハンズオンも、先にまとめて作ったあと切り分けていたなぁ、と。

これもバランスだとは思いますが、今後はComponentをもう少しまとめてみようと思います。

画像切り替え

以前は画像のパスをimgタグ渡して、動的に画像を切り替える、ということをしていましたが、
これには問題がありました。

最初にページを表示した時に、画像がキャッシュされていないので画像切り替え時に一瞬前の画像が見えたりしていたのです。

大量に画像を表示するなどの場合は別ですが、今回のように決まった数を表示する場合は、
画像を全てキャッシュしておいて、それを切り替えた方が良さそうです。

banner-image.ts

export interface BannerImage {
    imagePath: string;
    transitionPath: string;
    bannerState: string;
}

main-page.component.ts

import { Component, OnInit, HostListener } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations';
import { Title } from '@angular/platform-browser';
import { BannerImage } from '../banner-image' 

@Component({
  selector: 'app-main-page',
  templateUrl: './main-page.component.html',
  styleUrls: ['./main-page.component.css'],
  animations: [
    trigger('bannerState',
      [
        state('active', style({
          transform: 'scale(1.0)',
          filter: 'brightness(100%)',
          display: 'block'
        })),
        state('inactive', style({
          transform: 'scale(0.6)',
          filter: 'brightness(20%)',
          display: 'none'
        })),
        transition('active => inactive', animate('400ms ease-in')),
        transition('inactive => active', animate('10ms ease-in'))
      ]
    )]
})
export class MainPageComponent implements OnInit {
  private bannerImages: BannerImage[];
  private activeImageNum: number;
  private nextImageNum: number;
  
  constructor(private titleService: Title) {}
  ngOnInit() {
    this.titleService.setTitle('メインページ');

    this.bannerImages = [
      {imagePath: "assets/img/banner01.jpg", transitionPath: '', bannerState: 'inactive'},
      {imagePath: "assets/img/banner01.jpg", transitionPath: '', bannerState: 'inactive'},
      {imagePath: "assets/img/banner01.jpg", transitionPath: '', bannerState: 'inactive'},
    ];
    this.activeImageNum = - 1;
    this.nextImageNum = 0;
    this.onBannerChanged();
  }
  @HostListener('document:keyup', ['$event'])
  onArrowKeyUp(event: any){
    if(event.keyCode === 39){ // RightArrow.
      this.nextImageNum = (this.activeImageNum < this.bannerImages.length - 1)? this.activeImageNum + 1 : 0;
      this.bannerImages[this.activeImageNum].bannerState = 'inactive';
    }
    else if(event.keyCode === 37){ // LeftArrow
      this.nextImageNum = (this.activeImageNum > 0)? this.activeImageNum - 1: this.bannerImages.length - 1;
      this.bannerImages[this.activeImageNum].bannerState = 'inactive';
    }
  }
  onBannerChanged(){
    if(this.activeImageNum === this.nextImageNum){
      return;
    }
    this.bannerImages[this.nextImageNum].bannerState = 'active';
    this.activeImageNum = this.nextImageNum;
  }
}

main-page.component.html

< section id="top-banner-area" >
  < div *ngFor='let bannerImage of bannerImages' >
    < a href='bannerImage.transitionPath'>
      < img [src]="bannerImage.imagePath" class="top-banner" [@bannerState]="bannerImage.bannerState" (@bannerState.done)="onBannerChanged()" />
    < /a>
  < /div>
< /section>
< app-top-news>< /app-top-news>

参照

静的ファイルの読み込み

ページタイトル

ブラウザ対応

Component分割