Angular
Angularを使ったweb application作成の備忘録
Install
Mac OS
brew で node.js, npm をインストール.
$ brew install npm
npm で angular/cli をインストール.
$ npm install --global @angular/core
Upgrade to version 7 or later
https://www.techiediaries.com/updating-angular-cli-projects/ を参考。
Version 7.x.x にあげる場合、以下を実行する。
$ ng update @angular/cli@7 @angular/core@7
最新版にあげるなら、
$ ng update @angular/cli @angular/core
コマンドリファレンス
プロジェクトを新規作成
$ ng new "アプリケーション名" $ cd "アプリケーション名"
カレントディレクトリの下に "アプリケーション名"というディレクトリができる. src/app/ ディレクトリの下にソースコードのテンプレートが作られる.
コンポーネントを作る
https://angular.jp/cli/generate を参照。
$ ng generate component コンポーネント名
src/app/コンポーネント名 ディレクトリの下にソースコードのテンプレートが作られる.
例えば、HelloWorld という名前のコンポーネントを作ると、src/app/hello-world というディレクトリが作成され、その下に hello-world.component.{ts|html|css|spec.ts} というファイルが作られる。 これらのファイルを src/appの直下に作りたい場合は,--flat オプションを付ける.
サービスを作る
https://angular.jp/cli/generate を参照。
$ ng generate service サービス名
src/app ディレクトリの下にソースコードのテンプレートが作られる.
例えば、FileSystem というサービスを作ると、src/appディレクトリの下に file-system.service.{ts|spec.ts} というファイルが作られる。
開発用サーバーを起動する
https://angular.jp/cli/serve を参照。
$ ng serve
デプロイ用にビルドする
https://angular.jp/cli/build を参照。
$ ng build --base-href='/デプロイするサーバー上のpath/' --prod --aot
dist ディレクトリの下のファイルをサーバー上にデプロイする.
追加機能
rxjs
Angular アプリケーションから、外部の ajax サービスを呼び出すライブラリ。
アプリケーションディレクトリに移動して、以下のようにインストール。
$ npm install rxjs rxjs-compat
src/app/app.module.ts に以下を追加。
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
// ↓追加
import { HttpClientModule } from '@angular/common/http';
// ↑追加
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
// ↓追加
HttpClientModule,
// ↑追加
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Material
テーブルとかポップアップとかアニメーションとかを使いたい時は、https://material.angular.io/ の get started にしたがって angular/material をインストール。
$ npm install --save @angular/material @angular/cdk @angular/animations
src/app/app.module.ts に使いたいモジュールを追加。
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
// ↓追加
import { MatTableModule } from '@angular/material/table';
import { MatProgressSpinnerModule } from '@angular/material';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
// ↑追加
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
// ↓追加
MatTableModule,
MatProgressSpinnerModule,
BrowserAnimationsModule,
// ↑追加
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Tips
条件付き要素
ある条件が成り立っている場合にのみ表示される HTML要素が作りたいときは、*ngIfを使う。
コンポーネントのスニペット:
...
export class XxxComponent implements OnInit {
flag: boolean; // この値が True のとき、要素が表示される。
...
}
テンプレートのスニペット:
... <div *ngIf="flag"> this.flag が true のときのみ表示 </div> <div *ngIf="!flag"> this.flag が false のときのみ表示 </div>
true/false やswitch文のように条件が相互に排他的な場合は、上記のように容易に多分岐をさせることも可能。 しかし、if-else-if-else文のような、条件がカスケードするような多分岐を記述するのは簡単でない。
そのために、*ngIfにはelseを記述する方法が用意されている。
コンポーネントのスニペット:
...
export class XxxComponent implements OnInit {
data: string; // この値によって、表示するHTMLを切り替える。
...
}
テンプレートのスニペット:
...
<div *ngIf="data=='A'; else ELSE_1">
this.data が A のときのみ表示
</div>
<ng-template #ELSE_1>
<div *ngIf="data=='B'; else ELSE_2">
this.data が B の時のみ表示
</div>
</ng-template>
<ng-template #ELSE_2>
<div *ngIf="data=='C'; else ELSE_3">
this.data が C の時のみ表示
</div>
</ng-template>
<ng-template #ELSE_3>
<div>
this.data が A, B, C 以外の時のみ表示
</div>
</ng-template>
繰り返し要素
コンポーネントの属性が配列(ここでは array とする)があった時、その要素ごとに HTML要素が作りたいときは *ngForを使う。
例えば、<list> (<ul>, <ol>)の中の<li>とか、<table>の中の<tr>など。
コンポーネント (xxx.component.ts) のスニペット
export class XxxComponent implements OnInit {
array: SomeClass[]; // 繰り返しを作るデータ。型はなんでもよい。
}
テンプレート (xxx.component.html) のスニペット
<ul>
<li *ngFor="let element of array">{{ element.field }}</li>
</ul>
綺麗なテーブルはtable参照。
table
Material の table を使うと、ヘッダを固定したり、フッタに集計を表示して、テーブルの中身をスクロールさせたり、カラムをダイナミックに選んだり、行をソートしたりできる。
インストールは Material を参照。
コンポーネント (xxx.component.ts) のスニペット:
class MyRow {
name: string; // カラム1
age: number; // カラム2
height: number; // カラム3
hobby: string; // カラム4
}
export class XxxComponent implements OnInit {
tableData: MyRow[]; // テーブルに表示するデータ
displayedColumns = [ "NAME", "AGE", "HOBBY" ]; // 表示するカラム
ngOnInit() {
// ここである必要はないけど、どこかでデータをセット。外部サービスからデータを入手するの実用的な使い方。
this.tableData = [ { "name": "Andy", "age": 9, "height": 132.5, "hobby": "Video Game" },
{ ... }, ];
}
}
テンプレートのスニペット:
<table mat-table [dataSource]="tableData">
<!-- NAME column -->
<ng-container matColumnDef="NAME">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
<td mat-cell *matCellDef="let row">{{ row.name }}</td>
</ng-container>
<!-- AGE column -->
<ng-container matColumnDef="AGE">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Age</th>
<td mat-cell *matCellDef="let row">{{ row.age }}</td>
</ng-container>
<!-- HEIGHT column -->
<ng-container matColumnDef="HEIGHT">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Height</th>
<td mat-cell *matCellDef="let row">{{ row.height | number: '.1' }}</td> <!-- フォーマットが | で指定できる -->
</ng-container>
<!-- HOBBY column -->
<ng-container matColumnDef="HOBBY">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Hobby</th>
<td mat-cell *matCellDef="let row">{{ row.height }}</td>
</ng-container>
<!-- ここで行の繰り返しを指定 -->
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<tr mat-footer-row *matFooterRowDef="displayedColumns; sticky: true"></tr>
sortable table
上記のtableをソート可能なテーブルにする。
テンプレートのスニペット:
<table mat-table [dataSource]="tableData" matSort (matSortChange)="sortData($event)">
<!-- NAME column -->
<ng-container matColumnDef="NAME">
<th mat-header-cell *matHeaderCellDef mat-sort-header mat-sort-header>Name</th>
<td mat-cell *matCellDef="let row">{{ row.name }}</td>
</ng-container>
... 以下同様
コンポーネント (xxx.component.ts) のスニペット:
import { MatSort, Sort } import from '@angular/material/sort';
...
export class XxxComponent implements OnInit {
sortData(sort: Sort) {
const sort_dir = sort.diretion === 'asc' ? 1 : -1;
this.tableData = this.tableData.slice(); // データをコピーする。
const compare = (a, b) => { return (a < b ? -1 : 1) * sort_dir; };
switch (sort.active) {
case 'NAME':
this.tableData.sort((a, b) => { return compare(a.NAME, b.NAME); }); break;
case 'AGE':
this.tableData.sort((a, b) => { return compare(a.AGE, b.AGE); }); break;
case 'HEIGHT:
this.tableData.sort((a, b) => { return compare(a.HEIGHT, b.HEIGHT); }); break;
default:
break;
}
...
}