はじめに
この記事では Angular GETTING START と TUTORIAL を確認して学んだことを記載します。
TUTORIAL は実例を基にして基礎的なコーディング方法が学べますが、概念的な説明が不足している感があるため、FUNDAMENTALS を適宜確認する必要があると思われます。
尚、既に1カ月近く経過してしまっていますが '18/05/03 に Angular 6 が出たので使うことにしました。
Angular とは
Angular は HTML と TypeScript で記述されたアプリケーションをビルドするためのプラットフォーム/フレームワークです。Angular 自身も TypeScript で記述されています。
Angular を実行するための事前準備
Node.js 8以上, npm 5.x をインストールします。
Windows であれば nodist、Mac であれば nodebrew を使うと便利です。
Docker であれば node(official) を使うとよいでしょう。
Windows ユーザ向け事前準備方法
- nodist をインストールする
- Node.js, npm をインストールして global バージョンを設定する
$ nodist + 8
$ nodist global 8
- Node.js, npm のバージョンを確認する
$ node -v
$ npm -v
※プロンプトが>
ではないのはコピペした時にうっかりリダイレクトしてしまわないためです。
Angular CLI をインストールする
まずは Angular CLI をインストールします。
$ npm install -g @angular/cli
Angular CLI は Angular アプリケーションを作成したり、Angular アプリケーションのパーツとなるコンポーネントやサービスを CLI コマンドで生成する機能があります。
Angular の Hello world アプリケーションを作成する
アプリケーションとして動作するために必要なファイルは ng new
コマンド一発でもろもろインストールが出来ます。
$ ng new my-app # my-app は適当なアプリケーション名に変える
アプリケーション名は次の規則に従う必要があります。
(規則は網羅できているわけではないと思います。一度エラーとなったため備忘録として記載しています。)
- 使える文字は、アルファベットとハイフン
-
である - アルファベットで始まる
ng new
は、アプリケーション名のディレクトリを CWD 直下に作成し、その中にプロジェクトの初期ファイルを作成します。
README.md, package.json 等も作成されますが、既にある場合はコマンド実行が進まないので消しておくとよいでしょう。
ng new で作成されるファイル
ng new
を実行すると Angular を動作させるために必要なファイルが作成されますが、その他にも、テストに使う Karma の設定ファイル、Lint ツールの TSLint 用の設定ファイル、エディタ整形ルールの EditorConfig 用設定ファイル等が作成されます。
作成されるファイルは Angular > GETTING STARTED#project-file-review に説明されてますが、次のとおりです。
src/
app/
app.module.ts # AppModule の定義
# -> Angular にアプリケーションのコンパイル方法と起動方法を伝える役割
app.component.html # AppComponent のビューテンプレート(ビューテンプレート内はAppComponentコンポーネントの変数が参照できる)
app.component.spec.ts # AppComponent のユニットテスト
app.component.ts # AppComponent のコンポーネント定義
app.component.css # AppComponent のスタイルシート
assets/ # アプリケーションビルド時にコピーする画像を配置するディレクトリ
.gitkeep # Git 用の空ディレクトリをバージョン管理配下に置くためのファイル
environments/
environment.prod.ts # 本番環境でビルドするための設定
environment.ts # 本番以外の環境でビルドするための設定
favicon.ico # ブラウザのお気に入り用アイコン
index.html # root AppModule のビューファイル
main.ts # アプリケーションのエントリポイント
polyfills.ts # Pollyfill設定(ブラウザ毎のサポートレベルを統一するためのもの)
test.ts # ユニットテストのエントリポイント (内部で .spec.ts ファイルを読み込む)
styles.css # App 全体の CSS 設定を指定する
browserslist # 他の Front-end ツール間でターゲットブラウザを共有するためのファイル
karma.conf.js # Karma 用設定ファイル
tsconfig.app.json # TypeScript の App 用設定ファイル
tsconfig.spec.json # TypeScript のユニットテスト用設定ファイル
tslint.json # TSLint の設定ファイル
e2e/
src/
app.e2e-spec.ts # End-to-End(e2e) テストのエントリポイント
app.po.ts # app.e2e-spec.ts から読み込まれるファイル
protractor.conf.js # e2e テストの設定ファイル(内部で src/app.e2e-spec.ts を読み込む)
tsconfig.e2e.json # TypeScript の e2e テスト用設定ファイル
README.md # プロジェクトの README
angular.json # AngularCLI の設定ファイル
package.json # npm の設定ファイル
tsconfig.json # TypeScript の設定ファイル(IDE用)
tslint.json # TSLint の設定ファイル
.editorconfig # Editorconfig の設定ファイル
.gitignore # Git の管理対象外を記述ファイル
アプリケーション開発環境の実行方法 (ng serve)
ng new
は依存パッケージのインストールも行うため、ng new
を実行し終わると ng serve
コマンドにより Angular アプリケーションを起動することが出来ます。
$ cd my-app # 作成したアプリケーション名のディレクトリが作成されている
$ ng serve --open # --open を指定するとブラウザで URL が開かれます
これで http://localhost:4200/ にアクセスすると Angular のロゴと「Welcome to app!!」が表示されます。
Docker を使っている場合は $ docker run -it -v -p 4200:4200 node:8 bash
のように TCP 4200 番を publish しておき、 $ ng serve --host 0.0.0.0 --open
で Angular App を起動するとよいでしょう。
するとブラウザで http://${DockerホストのIPアドレス}:4200/ にアクセスできます。
尚、ng serve
を実行中にソースコードを編集すると自動でリロードされます。
アプリケーションを開発する際、 ng serve
で実行してコードを変更することを繰り返すことになります。
Angular アプリケーションの概要
アプリケーションが起動できたので編集を進めたいと思うでしょうが、その前に少し Fundamentals に記述された概念的な話をします。(理解している人は読み飛ばしてください)
Angular アプリケーションは NgModule
と呼ばれるモジュールによって構成されます。モジュールとはコンポーネントをコンパイルするためのコンテキストを定義するものであり、モジュールには起動する際の起点となるルートモジュールが必ず含まれ、その他はフィーチャモジュールと呼ばれます。
※ ここでのモジュールは JavaScript(ES2015) のモジュールとは異なります
コンポーネントはビュー(ビューテンプレートやスタイル)や、HTML をレンダリングするために必要となる処理を記述するものです。
その他、特定の HTML をレンダリングするためだけに必要な処理「以外」はサービスとして定義することができます。サービスはコンポーネントを初期化する際に注入(injection)することができ、コンポーネントモジュールからサービスを利用することが出来るようになります。
※ サービスとコンポーネントを分ける理由は再利用性を高めるためです。(Dependency Injection (DI) と呼ばれる)
※ サービスはサービスプロバイダによりコンポーネントに注入されます
ここで、Tutorial にある Hero 名を記録して表示・検索する Web アプリケーションの中から、Hero 一覧ページを表示する heroes.component.ts を例にとって、ルートモジュールとコンポーネントに記載される内容を紹介します。
angular-cli にコンポーネントを生成する機能があるため使うとよいでしょう。generate オプションを使って以下のように生成できます。また、ルートモジュールに組み込むところまで行ってくれます。
$ ng generate component Heroes
CREATE src/app/heroes/heroes.component.css (0 bytes)
CREATE src/app/heroes/heroes.component.html (25 bytes)
CREATE src/app/heroes/heroes.component.spec.ts (628 bytes)
CREATE src/app/heroes/heroes.component.ts (269 bytes)
UPDATE src/app.module.ts (396 bytes)
コンポーネントを追加しただけでは、まだブラウザでアクセスした時に表示することはできません。ルーティング設定を追加する必要があります。ルーティング設定には @angular/router を使います。
ルーティング設定を行うにはモジュールの中で RouterModule::forRoot
を使います。
例中では取り合えず利用するためにルートモジュールに直接定義して利用しますが、ベストプラクティスとして Tutorial にあるようにモジュールとして作成するのがよいでしょう。
(参考: https://angular.io/tutorial/toh-pt5#add-the-approutingmodule)
ルーティング設定が終わると、コンポーネントに <a routerLink="/heroes">Heroes</a>
を追加すると、作成したコンポーネントに対するリンクが作成できるようになります。このリンクを使ってページ遷移した場合、URL のパスが変わりブラウザをリロードしても遷移したページが読み込まれ、ページを遷移した履歴がブラウザに残るため、戻る・進むボタンでページ遷移が出来ます。
ルートモジュールの記述例
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; // inputのデータバインディングに使う FormsModule を読み込む
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; // Router ライブラリパッケージを読み込む
import { AppComponent } from './app.component';
import { HeroesComponent } from './heroes/heroes.component'; // Heroesコンポーネントを読み込む
const appRoutes: Routes = [ // パスを指す Routes 型インスタンスを作成
{ path: 'heroes', component: HeroesComponent }, // `/heroes` にアクセスした際に HeroesComponent を
// 読み込み、`<router-outlet />` を
// 置き換えて View を表示する
];
@NgModule({
declarations: [
AppComponent,
HeroesComponent, // Heroes コンポーネントをモジュールリストに追加する
],
imports: [
BrowserModule,
FormsModule, // ngModel ディレクティブを使うために FormsModule を指定する
RouterModule.forRoot( // Routerモジュールを作成する(provider込み)
appRoutes, // パスを指す Routes 型の引数を設定
{ enableTracing: true } // デバッグ用出力を有効にする
),
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
ルートモジュールには次の特徴があります。
- 慣例としてクラス名を AppModule とし、ファイル名を
src/app/app.module.ts
として定義する -
@NgModuel アノテーションを使い、
declarations
として利用するコンポーネント等、アプリケーションの全体像を指定する- Angular はこのモジュールに記載された内容から、アプリケーションをビルドするために必要な構成を読み取る
@NgModule
デコレータで定義する内容
- declarations
- NgModule に関連する directives/pipes のリストを指定する
- コンポーネントは 必ず唯1つの NgModule に紐く必要がある
- imports
- このモジュールのテンプレートで使う directives/pipes のリストを指定する
- リストには ModuleWithProviders もまた含まれる
- providers
- サービス(注入可能(injectable) なオブジェクト)のリストを指定する
- bootstrap
- モジュールが bootstrap される際に bootstrap されるコンポーネントのリストを指定する
詳細は https://angular.io/api/core/NgModule を参照のこと。
ルートコンポーネントのテンプレート記述例
<base href="/" />
<nav>
<li>
<h2><a routerLink="/heroes" routerLinkActive="active">Heroes</a></h2>
</li>
</nav>
<router-outlet></router-outlet>
コンポーネントの記述例
import { Component, OnInit } from '@angular/core'; // Componentデコレータと OnInitインタフェースの読み込み
import { Hero } from '../hero'; // Heroモデル定義の読み込み
import { HeroService } from '../hero.service'; // Heroサービスの読み込み
@Component({ // @Componentデコレータを使ってコンポーネントを定義
selector: 'app-heroes', // - コンポーネントが指すHTMLのタグ名を<app-heroes>とする
templateUrl: './heroes.component.html', // - ビューテンプレートとして./heroes.component.htmlを使う
styleUrls: ['./heroes.component.css'] // - スタイルシートとして./heroes.component.cssを使う
})
export class HeroesComponent implements OnInit { // OnInitインタフェースを継承してHeroesComponentクラスを定義して、exportする
heroes: Hero[]; // Heroモデル配列型のheroes変数を定義
selectedHero: Hero; // リスト内の選択中の Hero
constructor( // コンストラクタの定義
private heroService: HeroService // HeroServiceサービスを注入(インスタンスをheroServiceプロパティに代入する)
) { }
ngOnInit() { // lifecycle hook 関数の ngOnInit を実装
this.getHeroes();
}
/* Hero リストを取得する */
getHeroes(): void {
this.heroService.getHeroes() // heroServiceのgetHeroesメソッドを呼び出し heroes プロパティに代入する
.subscribe((heroes) => { this.heroes = heroes; });
}
/* Hero を選択する */
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
/* Hero リストに Hero を追加する */
add(name: string): void {
name = name.trim();
if (!name) { return; }
let newHero = new Hero();
newHero.id = Math.max.apply(null, this.heroes.map(h => h.id)) + 1;
newHero.name = name;
this.heroes.push(newHero); // リストに Hero を追加 (プロパティ変数を操作しているだけなのでリロードすると消える)
}
/* Hero リストから Hero を削除する */
delete(hero: Hero): void {
this.heroes = this.heroes.filter(h => h !== hero); // リストから指定したIDのHeroを削除
}
}
export class Hero {
id: number;
name: string;
}
button {
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
cursor: hand;
font-family: Arial;
}
: <snip>
テンプレートの例
テンプレートは HTML と Angular Markup を組み合わせたものです。
Angular Markup にはテンプレートディレクティブとバインディングマークアップ、そしてパイプがあります。これらは HTML を表示する前に Angular によって処理されるため、表示結果にロジックやデータバインディングを組み込むことが出来ます。
<h2>My Heroes</h2>
<div>
<label>Hero name:
<input #heroName /> <!-- input ボックスを用意し、入力した値を heroName で扱う -->
</label>
<!-- (click) passes input value to add() and then clears the input -->
<button (click)="add(heroName.value); heroName.value=''"> <!-- click 時にコンポーネントの add メソッドを呼び出す -->
add
</button>
</div>
<ul class="heroes">
<li *ngFor="let hero of heroes"> <!-- *ngFor で指定した変数 heroes から要素を1つずつ取り出して
hero に代入して li とその中の html を繰り返し表示する -->
が参照できる -->
<span class="badge">{{hero.id}}</span> {{hero.name}}
<button class="delete" title="delete hero"
(click)="delete(hero)">x</button> <!-- click 時にコンポーネントの delete メソッドを呼び出す -->
</li>
</ul>
<hr />
<h2>Hero Detail</h2>
<div *ngIf="selectedHero !== null"> <!-- selectedHero が null ではない場合だけ div とその中の html を表示する -->
<div>
<label>Hero name: {{selectedHero.name | uppercase }}</label> <!-- 名前を uppercase パイプにより全て大文字にして表示する -->
</div>
<div>ID: {{ selectedHero.id }}</div> <!-- ID を表示 -->
<div>Edit name: <input [(ngModel)]="selectedHero.name" /></div> <!-- selectedHero.name をデータバインディングして即時編集する -->
</dvi>
バインディングマークアップには2種類のタイプが存在し、それぞれ次の役割がある。
Angular は2方向(読み書き)データバインディングをサポートしています。
- Event binding
- ユーザのインプットに応じてアプリケーションデータを更新して応答する
- Property binding
- アプリケーションデータを HTML に埋め込む
サービスの例
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { Hero } from './hero';
import { HEROES } from './mock-heroes'; // 簡略のためモックデータを読み込む。サーバからデータを取得するのが一般的。
@Injectable({
providedIn: 'root', // root プロバイダによって生成されるよう定義する
})
export class HeroService {
getHeroes(): Observable<Hero[]> {
return of(HEROES);
}
}
import { Hero } from './hero';
export const HEROES: Hero[] = [
{ id: 11, name: 'Mr. Nice' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Celeritas' },
{ id: 15, name: 'Magneta' },
{ id: 16, name: 'RubberMan' },
{ id: 17, name: 'Dynama' },
{ id: 18, name: 'Dr IQ' },
{ id: 19, name: 'Magma' },
{ id: 20, name: 'Tornado' }
];
Angular アプリケーション表示例
上記で示した例を記述して Angular を起動すると次の表示結果となる。
/ の表示結果 |
/heroes の表示結果 |
---|---|
![]() |
![]() |
本番へのデプロイ
本番環境で動作させるためには ng build によりファイルをビルドした後、ビルドさせたファイルをコンテンツルートとして設定したHTTPサーバを起動する。
ng serve との違いはビルドサイズが小さいなるよう minify 等が行われる点である。
$ ng build --prod
上記コマンドが成功すると dist/my-app
(my-app はアプリケーション名) にファイルがビルドされる。(js ファイルは minify されたりする)
ファイルを適宜デプロイ先に展開した後、apache/nginx 等の HTTP サーバを起動する。
Docker を使っている場合は $ docker run -p 80:80 -v /usr/local/src/my-app/dist/my-app:/usr/share/nginx/html:ro -d nginx
等とすればよい。
(例は /usr/local/src/my-app にアプリケーションルートがある場合)
以上で http://${DockerホストのIPアドレス}
でデプロイしたアプリケーションにアクセスすることが出来る。
まとめ
- Angular CLI を使って初期アプリケーションを作成することが出来る
- コンポーネントを使ってアプリケーションデータを表示することが出来る
- テンプレート内では
{{}}
を使って変数が使える
- テンプレート内では
- Angular CLI を使ってコンポーネントを生成することが出来る
- ngModel ディレクティブにより双方向データバインディングが行える
- ngModel ディレクティブを使うためには FormsModule を読み込む必要がある
- ユーザが Hero を選択した場合だけ詳細情報を表示させた
- 選択操作はイベントバインディング
(click)
を使った - 条件に一致した場合のみ表示させるために
*ngIf
を使った
- 選択操作はイベントバインディング
- 配列を1つずつ取り出しながら全ての Hero リストを表示するために
*ngFor
を使った - HeroService をサービスプロバイダとして root プロバイダによって生成されるよう定義して、HeroComponent に注入を行った
- コンポーネントの ngOnInit ライフサイクルをフックして Hero リストを取得した
- Angular router を使って異なるコンポーネント間を移動した
- 移動した履歴はブラウザの進む・戻るで再現できる
-
<router-outlet>
タグがパスに関連付けられたコンポーネントの表示結果に置き換えられた - a タグに
routerLink
ディレクティブをつけてリンクを作成した
- Hero リストを追加・削除・編集可能にした
- 本番環境へデプロイするために
ng build --prod
コマンドを使った