Angular 7 と Angular Material と Material Design Icons を試した

Angular7 がリリースされたので、そのお試しがてら、Angular MaterialMaterial Design Icons を導入して遊んでみた。

目次

Angular7 とは

Angular7 は 2018-10-19 にリリースされた。

Angular6 からの破壊的変更はほとんどなく、Angular Update Guide で指示されるマイグレーション作業もほぼなし。ちなみに Angular6 は2018年10月から LTS モードになっていて、同時に Angular4 は LTS が終了。もう Angular4 系について書かれた書籍やサイトは「サポートが終了した古いフレームワークについて触れた記事」と化すのか…。

今回は @angular/cli を本稿執筆時点の最新版である v7.0.2 にアップデートし、そこから ng new コマンドで新規に Angular7 アプリを生成しようと思う。

Angular Material とは

Angular Material とは、Angular アプリで Material Design を実現するためのコンポーネント集。NgModule として各種コンポーネントをインポートし、マテリアル・デザインなボタンや Card UI などを実装できる。

コレまで Twitter Bootstrap ばっかりで、マテリアル・デザインに触れてこなかったのだが、Bootstrap がより「Web サイトっぽさ」を持つ UI コンポーネントが多いのに対して、マテリアル・デザインは「ネイティブアプリっぽさ」があるように感じる。元々の設計思想として、「レスポンシブルデザイン」とかいうよりは「様々な種類のデバイスで操作感に統一性をもたせる」ことが狙いっぽい。

Material Design Icons とは

Material Design Icons とは、Material Design の中で使われるアイコン集。Bootstrap3 の Glyphicon や、Font Awesome みたいなモノの Material Desgin 版だと思って良い。どうもコレだけ Angular Material の中には含まれていないようなので、別途インストールしてみる。

Angular7 アプリを新規作成する

それではいよいよ、Angular7 アプリを作成し、Angular Material と Material Design Icons を導入してみようと思う。OS は macOS Mojave で試したが、Windows10 でも同じ。

Node.js、npm の実行環境は以下のとおり。Angular7 からは Node.js v10 がサポートされたようだ。

$ node -v
v10.7.0

$ npm -v
6.1.0

まずは Angular CLI をグローバルインストールする。

$ npm install -g @angular/cli

# バージョン確認
$ ng version

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/

Angular CLI: 7.0.2
Node: 10.7.0
OS: darwin x64
Angular:
...

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.10.2
@angular-devkit/core         7.0.2
@angular-devkit/schematics   7.0.2
@schematics/angular          7.0.2
@schematics/update           0.10.2
rxjs                         6.3.3
typescript                   3.1.3

次に ng new コマンドでプロジェクトを作成する。Angular7 から、--style--routing についてはオプション引数で指定せず対話形式でも設定できるようになった。

$ ng new
? What name would you like to use for the project? practice-material
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS   [ http://sass-lang.com   ]
CREATE practice-material/README.md (1033 bytes)
CREATE practice-material/angular.json (3958 bytes)
CREATE practice-material/package.json (1324 bytes)
# …以下略…

プロジェクトが生成できたので、生成したプロジェクトのディレクトリに移動する。

Angular Material を導入する

続いて Angular Material を導入する。

公式リファレンスによると ng add で追加できるようだったので試してみたのだが、Cannot read property 'endTag' of undefined エラーが出て処理が完了しなかった。

$ npm run ng add @angular/material

> practice-material@0.0.0 ng /Users/Neo/Me/Practices/practice-material
> ng "add" "@angular/material"

Installing packages for tooling via npm.
? Enter a prebuilt theme name, or "custom" for a custom theme: indigo-pink
? Set up HammerJS for gesture recognition? Yes
? Set up browser animations for Angular Material? Yes
UPDATE package.json (1447 bytes)
up to date in 9.24s
HammerJS is already imported in the project main file (src/main.ts).
Cannot read property 'endTag' of undefined
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! practice-material@0.0.0 ng: `ng "add" "@angular/material"`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the practice-material@0.0.0 ng script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/Neo/.npm/_logs/2018-10-24T05_11_12_732Z-debug.log

package.json の書き換えだけ行われていて、次のように @angular/cdk@angular/materialhammerjs が追加されていた。

   "dependencies": {
     "@angular/animations": "~7.0.0",
+    "@angular/cdk": "^7.0.1",
     "@angular/common": "~7.0.0",
     "@angular/compiler": "~7.0.0",
     "@angular/core": "~7.0.0",
     "@angular/forms": "~7.0.0",
     "@angular/http": "~7.0.0",
+    "@angular/material": "7.0.1",
     "@angular/platform-browser": "~7.0.0",
     "@angular/platform-browser-dynamic": "~7.0.0",
     "@angular/router": "~7.0.0",
     "core-js": "^2.5.4",
+    "hammerjs": "^2.0.8",
     "rxjs": "~6.3.3",
     "zone.js": "~0.8.26"
   },

なんだか違和感があるので、素直に npm install からの手作業でやり直すことにする。

$ npm install --save @angular/material @angular/cdk hammerjs

これで npm install は完了。

続いて、AppModule に UI アニメーション用のモジュールとして BrowserAnimationsModule をインポートする。もしアニメーションさせたくない場合は NoopAnimationsModule をインポートする。

/* app.module.ts */

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

// 追加
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
// もしくは以下
// import { NoopAnimationsModule } from '@angular/platform-browser/animations';

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

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,  // もしくは NoopAnimationsModule
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

次に styles.scss でテーマを設定する。用意されているテーマは以下の4種類。

テーマごとのカラーリングは以下の記事にスクリーンショットがあるので確認できる。

/* styles.scss */

// Angular Material : Indigo Pink
@import '~@angular/material/prebuilt-themes/indigo-pink.css';

最後に、Hammer.js をインポートする。普段と違い、src/main.ts に追記するので注意。

/* main.ts */

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

// 以下を追記
import 'hammerjs';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

コレで Angular Material を利用する準備は整った。

Material Design Icons を導入する

引き続き、Material Design Icons も導入してみよう。

公式のリファレンスでは link 要素を追加して CDN から利用するように書かれているのだが、今回は npm install して使ってみようと思う。

# 本稿執筆時点では v3.0.1 がインストールされた
$ npm install --save material-design-icons

次に styles.scss でインポート。

/* styles.scss */

// 先程追加した Angular Material のテーマ
@import '~@angular/material/prebuilt-themes/indigo-pink.css';

// Material Desgin Icons のスタイルシートファイルをインポートする
@import '~material-design-icons/iconfont/material-icons.css';

コレで Material Design Icons を使うための準備が整った。

サンプルコードを書いてみる

今回は簡単にするため、AppComponent に直接コードを書いて、Angular Material および Material Design Icons を試してみる。

まずは Angular Material だが、コレは使用したいコンポーネントに合わせて NgModule をインポートする必要がある。公式の API リファレンスに import する NgModule クラスが記載されているので、よく確認する。

今回はこのあと色々なコンポーネントを書いていくため、以下のような AppModule にする。

import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

// Angular Material
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatInputModule } from '@angular/material/input';
import { MatCardModule } from '@angular/material/card';
import { MatSliderModule } from '@angular/material/slider';
import { MatToolbarModule } from '@angular/material/toolbar';

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

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,  // For Angular Material
    FormsModule,
    ReactiveFormsModule,
    // Angular Material --------------------
    MatButtonModule,
    MatCheckboxModule,
    MatInputModule,
    MatCardModule,
    MatSliderModule,
    MatToolbarModule,
    // App --------------------
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

次に HTML 部分。

<!-- app.component.html -->

<!-- ボタン -->
<p>
  <button mat-raised-button>Basic</button>
  <button mat-raised-button color="primary">Primary</button>
  <button mat-raised-button color="accent">Accent</button>
  <button mat-raised-button color="warn">Warn</button>
  <button mat-raised-button disabled>Disabled</button>
  <a mat-raised-button [routerLink]="['/']">Link</a>
</p>

<!-- カード -->
<mat-card>
  <mat-card-header>
    <mat-card-title>Slider Configuration</mat-card-title>
    <mat-card-subtitle>Slider Example</mat-card-subtitle>
  </mat-card-header>
  <mat-card-content>
    <section>
      <!-- テキストボックス -->
      <mat-form-field><input matInput type="number" placeholder="Value"     [(ngModel)]="value"></mat-form-field>
      <mat-form-field><input matInput type="number" placeholder="Min value" [(ngModel)]="min"></mat-form-field>
      <mat-form-field><input matInput type="number" placeholder="Max value" [(ngModel)]="max"></mat-form-field>
      <mat-form-field><input matInput type="number" placeholder="Step size" [(ngModel)]="step"></mat-form-field>
    </section>
    <section>
      <!-- チェックボックス -->
      <mat-checkbox [(ngModel)]="showTicks">Show ticks</mat-checkbox>
      <mat-checkbox [(ngModel)]="autoTicks" *ngIf="showTicks">Auto ticks</mat-checkbox>
      <mat-form-field *ngIf="showTicks && !autoTicks"><input matInput type="number" placeholder="Tick interval" [(ngModel)]="tickInterval"></mat-form-field>
    </section>
    <section><mat-checkbox [(ngModel)]="thumbLabel">Show thumb label</mat-checkbox></section>
    <section><mat-checkbox [(ngModel)]="vertical">Vertical</mat-checkbox></section>
    <section><mat-checkbox [(ngModel)]="invert">Inverted</mat-checkbox></section>
    <section><mat-checkbox [(ngModel)]="disabled">Disabled</mat-checkbox></section>
  </mat-card-content>
</mat-card>
<mat-card>
  <mat-card-content>
    <h2>Result</h2>
    <!-- スライダー (Hammer.js 利用) -->
    <mat-slider [(ngModel)]="value" [disabled]="disabled" [invert]="invert" [max]="max" [min]="min" [step]="step" [thumbLabel]="thumbLabel" [tickInterval]="tickInterval" [vertical]="vertical"></mat-slider>
  </mat-card-content>
</mat-card>

Button は単独で配置。Slider のオプションを切り替えるため Input、Checkbox を利用。フォーム部品を使うために Form Field を import し、Card でレイアウトを調整した。このコードはほとんど公式の API リファレンスにあるサンプルコードの流用。ちなみに、mat-* なコンポーネント名称が最近の Angular Material の仕様で、昔は md-* というコンポーネント名だったそうなので、参考にする文献の年代に注意。

続いてコレに対応するプロパティを TypeScript 側に書く。

/* app.component.ts */

import { Component } from '@angular/core';
import { coerceNumberProperty } from '@angular/cdk/coercion';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  public autoTicks = false;
  public disabled = false;
  public invert = false;
  public max = 100;
  public min = 0;
  public showTicks = false;
  public step = 1;
  public thumbLabel = false;
  public value = 0;
  public vertical = false;
  private _tickInterval = 1;
  get tickInterval(): number | 'auto' {
    return this.showTicks ? (this.autoTicks ? 'auto' : this._tickInterval) : 0;
  }
  set tickInterval(value) {
    this._tickInterval = coerceNumberProperty(value);
  }
}

公式のサンプルコードの受け売り。coerceNumberProperty() っていうのは NaN を弾くためのちょっとしたヘルパーメソッド。常に number を返すためのモノと思っておけば良い。

コレで完成。動作するスライダーができた。

Material Desgin Icons を使ってみる

Material Design Icons の方は、以下のページより使用可能なアイコンを確認できる。

アイコンをクリックし、画面左下の「Selected Icon」を選ぶと、コピペで使えるコードが表示される。

Material Design Icons は、Bootstrap Glyphicons や Font Awesome のようにクラス名でアイコンを指定するのではなく、<i class="material-icons"></i> 内に書いた文字列によってアイコン文字が生成される、という特殊な作りになっている。

HTML に、以下のようにいくつかアイコンを書いてみよう。

<!-- app.component.html -->
<i class="material-icons">code</i>
<i class="material-icons">done</i>
<i class="material-icons">favorite</i>

このように実装すると、code で「< >」こんなアイコン、done で「レ点」チェック、favorite でハートマークが表示されることが確認できる。いずれも .material-icons というクラス名で実装してあることが分かるだろう。

このような特殊なウェブフォントなので、オススメはしないが、以下のように書いても同じ3つのアイコンが表示される。

<!-- スペースがなくても良い -->
<i class="material-icons">codedonefavorite</i>

<!-- スペース有 -->
<i class="material-icons">code done favorite</i>

アイコンを含む文字列をコピーしてメモ帳などに貼り付けると、中に書いた文字列が登場するのが面白い。

お試しできた

今回はココまで。

Angular7 は Angular6 からの劇的な変化はないものの、AngularCLI はより親切な作りになっているし、今回は試していないが複数プロジェクトやライブラリの作成も良い感じになってきた。Tree-Shaking を始めとするバンドルサイズ (ビルドした最終成果物ファイルのサイズ) を小さくする改良も重ねられていて良い。

Angular Material (Material Design)、なかなか良いではないか…。ただ、Angular Material は Bootstrap のようにデフォルトスタイルを調整したりする機能はなく、本当にコンポーネント単位で完結しているので、ページレイアウトなんかはスクラッチで実装しないといけない部分がある。今回試してはいないが、Normalize.css や Bootstrap (Reboot) でリセット / ノーマライズと全体の調整を任せておくやり方で大丈夫みたい。

Material Design Icons も面白い。当然 Angular Material と調和の取れるアイコンが多いので、使いやすい。