This is a sample of Angular application.
Id | Name | |
---|---|---|
1 | Yamada | yamada@example.com |
2 | Suzuki | suzuki@example.com |
3 | Tanaka | tanaka@example.com |
Id | 1 |
---|---|
Name | |
Are you sure you want to update this user?
このチュートリアルでは、最終的に下記の様なイメージの画面を作成します。メニューやユーザ名をクリックしてください。値までは変化しませんが、これから作成する画面のおおよそのイメージをつかむことができます。
This is a sample of Angular application.
Id | Name | |
---|---|---|
1 | Yamada | yamada@example.com |
2 | Suzuki | suzuki@example.com |
3 | Tanaka | tanaka@example.com |
Id | 1 |
---|---|
Name | |
Are you sure you want to update this user?
チュートリアルで作成するソースコードは下記からダウンロードできます。
Node.js 系のパッケージ管理ツール npm を使用して、Angular の CLI (Command Line Interface) をインストールします。
$ npm install -g @angular/cli
ng コマンドで新しいアプリケーションを作成します。my-app は作成するアプリケーション名となります。
$ ng new my-app
アプリケーションをビルドし、http://127.0.0.1:4200/ でアクセス可能なサーバを起動します。起動にはしばらく時間がかかります。ブラウザでアクセスすると、Angular のサンプル画面が表示されます。LISTEN する IPアドレスやポート番号を変更するには、--host, --port オプションを指定します。他のマシンからアクセスする際は、firewall-cmd などでポートの穴あけが必要な場合があります。
$ cd my-app
$ ng serve // --host 0.0.0.0 --port 3000
ソースを下記の様に書き換え、Hello world! を表示します。サーバが起動していれば、ソースファイルを修正すると自動的にリビルドと、ブラウザの再表示が行われます。
<h1>Hello world!</h1>
* { margin: 0; padding: 0; } h1 { font-size: 1.4em; }
ブラウザが自動的にリロードされ、Hello world! が表示されます。
ヘッダ、メニュー、ダッシュボードなどの画面要素をコンポーネントとして追加します。g は generate の略です。
$ ng g component header $ ng g component menu $ ng g component dashboard
app コンポーネントの HTML を下記の様に書き換えます。
<app-header></app-header> <app-menu></app-menu> <app-dashboard></app-dashboard>
ヘッダは黒地に白文字とし、Angular のアイコンを表示します。
<div class="header"> <img src="../favicon.ico" alt="Angular"> Angular Sample Console </div>
.header { padding: 4px; background-color: #000; color: #fff; } .header img { width: 18px; vertical-align: middle; }
メニューには Dashboar メニューを記述します。
<div class="menu"> <ul> <li><a href="/">Dashboard</a></li> </ul> </div>
.menu { background-color: #ccc; } .menu li { padding: 4px 8px; display: inline-block; }
ダッシュボードにはタイトルを表示します。
<h1>Dashboard</h1>
ヘッダ、メニューバー、ダッシュボードタイトルが表示されれば成功です。
{{変数名}} で、コンポーネントが持つ変数を表示することができます。各ソースを下記の様に修正してください。
: export class DashboardComponent implements OnInit { message: string; constructor() { this.message = 'This is a sample of Angular application.'; } :
<h1>Dashboard</h1> <p>{{message}}</p>
Dashboard 画面にコンポーネントで定義した message 変数の値が表示されれば成功です。
URL によって下記の様に表示を切り替えます。表示の切り替えにはルーティングモジュールを使用します。
まず、ルーティングモジュールを追加します。
$ ng g module app-routing --flat --module=app
ユーザ一覧、ユーザ編集コンポーネントを追加します。
$ ng g component user/user-list $ ng g component user/user-edit
各ファイルを下記の様に修正します。
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule, Routes } from '@angular/router'; import { DashboardComponent } from './dashboard/dashboard.component'; import { UserListComponent } from './user/user-list/user-list.component'; import { UserEditComponent } from './user/user-edit/user-edit.component'; const routes: Routes = [ { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, { path: 'dashboard', component: DashboardComponent }, { path: 'users', component: UserListComponent }, { path: 'users/:id/edit', component: UserEditComponent }, ]; @NgModule({ imports: [ CommonModule, RouterModule.forRoot(routes), ], exports: [ RouterModule, ], declarations: [] }) export class AppRoutingModule { }
<app-header></app-header> <app-menu></app-menu> <div class="main"> <router-outlet></router-outlet> </div>
<div class="menu"> <ul> <li><a routerLink="/">Dashboard</a></li> <li><a routerLink="/users">Users</a></li> </ul> </div>
<h1>Users</h1>
Dashboard メニューをクリックすると Dashboard 画面が、Users メニューをクリックすると Users 画面(タイトルのみ)が表示されれば成功です。
Users 画面にユーザ一覧を表示します。まず、User オブジェクトのクラスを定義します。
export class User { id: number; name: string; email: string; }
UserList コンポーネントで users リストを初期化します。
import { Component, OnInit } from '@angular/core'; import { User } from '../user'; @Component({ selector: 'app-user-list', templateUrl: './user-list.component.html', styleUrls: ['./user-list.component.css'] }) export class UserListComponent implements OnInit { users: User[]; constructor() { } ngOnInit() { this.users = [ { id: 1, name: 'Yamada', email: 'yamada@example.com' }, { id: 2, name: 'Suzuki', email: 'suzuki@example.com' }, { id: 3, name: 'Tanaka', email: 'tanaka@example.com' }, ]; } }
UserList 画面で、users リストを表示します。*ngFor は配列を繰り返し表示する際に使用されるディレクティブです。
<h1>Users</h1> <table> <tr><th>Id</th><th>Name</th><th>Email</th></tr> <tr *ngFor="let user of users"> <td>{{user.id}}</td> <td>{{user.name}}</td> <td>{{user.email}}</td> </tr> </table>
すこし、見栄えを定義します。
* { margin: 0; padding: 0; } h1 { font-size: 1.4em; } a:link, a:visited { color: #000; } .main { padding: 8px; } table { border-collapse: collapse; width: 100%; } table th, table td { border: 1px solid #ccc; padding: 4px; } table th { background-color: #ddd; text-align: left; }
Users 画面に、ユーザの一覧が表示されれば成功です。
コンポーネント間でデータを共有したりするために、サービスを作成します。下記の例では、ユーザ一覧画面、ユーザ編集画面で共有するユーザ情報を管理する user サービスを作成します。
$ ng g service user/user
app.module.ts に user サービスを追加します。
: import { AppRoutingModule } from './/app-routing.module'; import { UserService } from './user/user.service'; import { UserListComponent } from './user/user-list/user-list.component'; : @NgModule({ : providers: [ UserService ], :
サービスに、初期化、一覧取得、詳細取得、設定用のメソッドを追加します。
import { Injectable } from '@angular/core'; import { User } from './user'; @Injectable() export class UserService { users: User[]; constructor() { this.users = [ { id: 1, name: 'Yamada', email: 'yamada@example.com' }, { id: 2, name: 'Suzuki', email: 'suzuki@example.com' }, { id: 3, name: 'Tanaka', email: 'tanaka@example.com' }, ]; } getUsers(): User[] { return this.users; } getUser(id: number): User { return this.users.find(user => user.id === id); } setUser(user: User): void { for (let i = 0; i < this.users.length; i++) { if (this.users[i].id === user.id) { this.users[i] = user; } } } }
import { Component, OnInit } from '@angular/core'; import { User } from '../user'; import { UserService } from '../user.service'; : export class UserListComponent implements OnInit { users: User[]; constructor( private service: UserService ) { } ngOnInit() { this.users = this.service.getUsers(); } }
この時点ではまだ画面には変動はありません。
ユーザ編集画面でフォームを取り扱います。まず、名前をクリックすると、/users/:id/edit に割り当てられたユーザ編集画面に遷移するようにします。
<h1>Users</h1> <table> <tr><th>Id</th><th>Name</th><th>Email</th></tr> <tr *ngFor="let user of users"> <td>{{user.id}}</td> <td><a routerLink="/users/{{user.id}}/edit">{{user.name}}</a></td> <td>{{user.email}}</td> </tr> </table>
app.modules.ts に FormsModule を組み込みます。
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; : @NgModule({ : imports: [ BrowserModule, AppRoutingModule, FormsModule ], :
ユーザ編集画面のコンポーネントを定義します。
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { User } from '../user'; import { UserService } from '../user.service'; @Component({ selector: 'app-user-edit', templateUrl: './user-edit.component.html', styleUrls: ['./user-edit.component.css'] }) export class UserEditComponent implements OnInit { user: User; constructor( private route: ActivatedRoute, private router: Router, private service: UserService ) { } ngOnInit() { const id = Number(this.route.snapshot.paramMap.get('id')); this.user = this.service.getUser(id); } onSubmit(form: any): void { let user = { id: form.id, name: form.name, email: form.email }; this.service.setUser(user); this.router.navigate(["/users"]); } }
ユーザ編集画面の HTML を下記の様に書き換えます。
<form #f="ngForm" (ngSubmit)="onSubmit(f.value)"> <input type="hidden" name="id" [ngModel]="user.id"> <table> <tr><th>Id</th><td>{{user.id}}</td></tr> <tr><th>Name</th><td><input type="text" name="name" [ngModel]="user.name"></td></tr> <tr><th>Email</th><td><input type="text" name="email" [ngModel]="user.email"></td></tr> </table> <button type="submit">OK</button> <button routerLink="/users">Cancel</button> </form>
見栄えを少し追記。
: button { margin-top: 4px; padding: 4px; min-width: 120px; } input[type="text"] { padding: 4px; width: 300px; }
ユーザ名をクリックするとユーザ編集画面が表示され、名前やメールアドレスを修正すると、一覧に反映されるようになれば成功です。
Angular Material という部品群の中から MatDialog というモーダルダイアログを利用してみます。まず、Angular/materia と Angular/CDK (Component Dev. Kit) をインストールします。
$ npm install --save @angular/material @angular/cdk
Angular Material 用の CSS ファイルを読み込みます。
@import '~@angular/material/prebuilt-themes/deeppurple-amber.css'; * { margin: 0; padding: 0; }
モーダルダイアログのサンプルとして、コンファームダイアログコンポーネントを作成してみます。
$ ng g component tools/confirm-dialog
各ファイルを次のように修正してください。
<h1>{{data.title}}</h1> <p>{{data.message}}</p> <button (click)="closeDialog(false)">Cancel</button> <button (click)="closeDialog(true)">OK</button>
import { Component, OnInit, Inject } from '@angular/core'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; @Component({ selector: 'app-confirm-dialog', templateUrl: './confirm-dialog.component.html', styleUrls: ['./confirm-dialog.component.css'] }) export class ConfirmDialogComponent implements OnInit { constructor( public dialogRef: MatDialogRef<ConfirmDialogComponent>, @Inject(MAT_DIALOG_DATA) public data: any ) { } ngOnInit() { } closeDialog(result) { this.dialogRef.close(result); } }
: import { FormsModule } from '@angular/forms'; import { MatDialogModule } from '@angular/material/dialog'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; : @NgModule({ : imports: [ : FormsModule, MatDialogModule, BrowserAnimationsModule ], providers: [ UserService ], entryComponents: [ ConfirmDialogComponent ], bootstrap: [AppComponent] }) export class AppModule { }
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; import { ConfirmDialogComponent } from '../../tools/confirm-dialog/confirm-dialog.component'; : export class UserEditComponent implements OnInit { constructor( private route: ActivatedRoute, private router: Router, private service: UserService, private dialog: MatDialog ) { } : onSubmit(form: any): void { let user = { id: form.id, name: form.name, email: form.email }; let dialogRef = this.dialog.open(ConfirmDialogComponent, { width: '300px', data: { title: 'Confirm', message: 'Are you sure you want to update this user?' } }); dialogRef.afterClosed().subscribe(result => { if (result === true) { this.service.setUser(user); this.router.navigate(["/users"]); } }); }
ユーザ編集画面で [OK] ボタンを押した際、本当に変更するか確認ダイアログが表示されれば成功です。ここまでのソースを、angular-sample-1.zip としてまとめてあります。
上記までの例では、ブラウザ側の JavaScript 内にダミーデータを保持し、それを書き換えるのみでしたが、ユーザ情報をサーバから REST-API で読み出し・格納できるように拡張していきます。まず、Express を用いて簡単な REST-API サーバを作成します。
$ mkdir ~/rest-test
$ cd ~/rest-test
$ npm init // 質問にはすべて Enter
$ npm install express
$ vi index.js
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
app.listen(8080);
app.use(bodyParser.json());
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*"); // セキュリティリスク有り
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
let users = [
{ id: 1, name: "Yamada", email: "yamada@example.com" },
{ id: 2, name: "Tanaka", email: "tanaka@example.com" },
{ id: 3, name: "Suzuki", email: "suzuki@example.com" }
];
app.get('/users', function(req, res) {
res.send(JSON.stringify(users));
});
app.post('/users', function(req, res) {
users.push(req.body);
res.end();
});
app.get('/users/:id', function(req, res) {
for (let i = 0; i < users.length; i++) {
if (users[i].id == req.params.id) {
res.send(JSON.stringify(users[i]));
}
}
});
app.post('/users/:id', function(req, res) {
for (let i = 0; i < users.length; i++) {
if (users[i].id == req.params.id) {
users[i] = req.body;
}
}
res.end();
});
app.delete('/users/:id', function(req, res) {
for (let i = 0; i < users.length; i++) {
if (users[i].id == req.params.id) {
users.splice(i, 1);
}
}
res.end();
});
$ node index.js
別のコンソールから動作を確認します。
# ユーザ一覧 $ curl -s -X GET http://localhost:8080/users | python -mjson.tool # ユーザ追加 $ curl -s -X POST -H 'Content-Type: application/json' \ -d '{"id":4,"name":"Sasaki","email":"sasaki@example.com"}' \ http://127.0.0.1:8080/users # ユーザ更新 $ curl -s -X POST -H 'Content-Type: application/json' \ -d '{"id":4,"name":"Sasaki2","email":"sasaki2@example.com"}' \ http://127.0.0.1:8080/users/4 # ユーザ削除 $ curl -s -X DELETE http://127.0.0.1:8080/users/4
REST-API サーバと非同期通信を行うために、RxJS というライブラリを利用します。URL に指定するアドレスは適切に変更してください。ブラウザから見たサーバのアドレスを指定します。
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { catchError, map, tap } from 'rxjs/operators';
import { User } from './user';jj
@Injectable()
export class UserService {
users: User[];
private url = 'http://127.0.0.1:8080'; // 適切に変更してください
constructor(
private http: HttpClient
) { }
getUsers(): Observable<User[]> {
return this.http.get<User[]>(`${this.url}/users`)
.pipe(
catchError(this.handleError('getUsers', []))
);
}
getUser(id: number): Observable<User> {
return this.http.get<User>(`${this.url}/users/${id}`)
.pipe(
catchError(this.handleError<User>(`getUser id=${id}`))
);
}
setUser(user: User): Observable<User> {
const id = user.id;
return this.http.post<User>(`${this.url}/users/${id}`, user)
.pipe(
catchError(this.handleError<User>(`setUser id=${id}`))
);
}
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
console.error(error);
console.log(`${operation} failed: ${error.message}`);
return of(result as T);
};
}
}
: import { FormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; : @NgModule({ : imports: [ BrowserModule, AppRoutingModule, FormsModule, HttpClientModule, : ], :
import { Component, OnInit } from '@angular/core'; import { User } from '../user'; import { UserService } from '../user.service'; @Component({ selector: 'app-user-list', templateUrl: './user-list.component.html', styleUrls: ['./user-list.component.css'] }) export class UserListComponent implements OnInit { users: User[]; constructor( private service: UserService ) { } ngOnInit() { this.service.getUsers().subscribe(res => { this.users = res; }); } }
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { User } from '../user'; import { UserService } from '../user.service'; @Component({ selector: 'app-user-edit', templateUrl: './user-edit.component.html', styleUrls: ['./user-edit.component.css'] }) export class UserEditComponent implements OnInit { user: User = { id: 0, name: '', email: '' }; constructor( private route: ActivatedRoute, private router: Router, private service: UserService ) { } ngOnInit() { const id = Number(this.route.snapshot.paramMap.get('id')); this.service.getUser(id).subscribe(res => { this.user = res; }); } onSubmit(form: any): void { : dialogRef.afterClosed().subscribe(result => { if (result === true) { this.service.setUser(user).subscribe(() => { this.router.navigate(["/users"]); jj }); } }); } }
サーバが保持するユーザデータを、一覧表示、編集することが可能となりました。ここまでのプログラム(src)配下を angular-sample-2.zip としてダウンロードできます。