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?
Angular 2.0 2016年9月14日 Angular 4.0 2016年12月13日 Angular 5.0 2017年11月1日 Angular 6.0 2018年5月4日 Angular 7.0 2018年10月18日 Angular 8.0 2019年5月28日 Angular 9.0 2020年2月6日 Angular 10.0 2020年6月24日 Angular 11.0 2020年11月11日 Angular 12.0 2021年5月13日 Angular 13.0 2021年11月3日
下記の環境で検証しています。
OS: Ubuntu 20.04 Node.js: 16.1.0 npm: 7.24.0 Angular CLI: 13.2.0
このチュートリアルでは、最終的に下記の様なイメージの画面を作成します。メニューやユーザ名をクリックしてください。値までは変化しませんが、これから作成する画面のおおよそのイメージをつかむことができます。
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 : how to change this setting, see http://angular.io/analytics. (y/N) N
ng コマンドで新しいアプリケーションを作成します。my-app は作成するアプリケーション名となります。Angular routing は後程利用するので y と答えてください。スタイルシートを CSS, SCSS などから選べます。オススメは SCSS ですが、とりあえずデフォルトの CSS で。
$ ng new my-app
? Would you like to add Angular routing? (y/N) y
? Which stylesheet format would you like to use? CSS # カーソル移動キーで選択してEnter
アプリケーションをビルドして開発用簡易サーバを起動します。起動にはしばらく時間がかかります。ブラウザでアクセスすると、Angular のサンプル画面が表示されます。LISTEN する IPアドレスやポート番号を変更するには、--host, --port オプションを指定します。他のマシンからアクセスする際は、firewall-cmd などでポートの穴あけが必要な場合があります。
$ cd my-app $ ng serve --host 0.0.0.0 --port 8080
ブラウザから http://{サーバアドレス}:8080/ にアクセスして Angular の画面が表示されれば成功です。
ソースを下記の様に書き換え、Hello world! を表示します。サーバが起動していれば、ソースファイルを修正すると自動的にリビルドと、ブラウザの再表示が行われます。
<h1>Hello world!</h1>
* { margin: 0; padding: 0; } h1 { font-size: 1.4em; }
ブラウザが自動的にリロードされ、Hello world! が表示されます。
ヘッダ、メニュー、ダッシュボードなどの画面要素をコンポーネントとして追加します。
$ ng generate component header $ ng generate component menu $ ng generate 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 generate module app-routing --flat --module=app
ユーザ一覧、ユーザ編集コンポーネントを追加します。
$ ng generate component user/user-list $ ng generate component user/user-edit
各ファイルを下記の様に修正します。
import { NgModule } from '@angular/core'; import { Routes, RouterModule } 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 }, ]; :
<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; constructor() { this.id = 0; this.name = ""; this.email = ""; } }
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(): void { 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 generate service user/user
app.module.ts に user サービスを追加します。
: import { DashboardComponent } from './dashboard/dashboard.component'; 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 | undefined { 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(): void { 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 { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; 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 = { id: 0, name: '', email: '' }; constructor( private route: ActivatedRoute, private router: Router, private service: UserService ) { } ngOnInit(): void { const id = Number(this.route.snapshot.paramMap.get('id')); var user = this.service.getUser(id); if (user) { this.user = user; } } 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; margin-right: 4px; padding: 4px; min-width: 120px; } input[type="text"] { padding: 4px; width: 300px; }
ユーザ名をクリックするとユーザ編集画面が表示され、名前やメールアドレスを修正すると、一覧に反映されるようになれば成功です。
Angular Material という部品群の中から MatDialog というモーダルダイアログを利用してみます。まず、Angular/material と 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 generate component tools/confirm-dialog
各ファイルを次のように修正してください。
: 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, 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(): void { } closeDialog(result: boolean) { this.dialogRef.close(result); } }
<h1>{{data.title}}</h1> <p>{{data.message}}</p> <button (click)="closeDialog(false)">Cancel</button> <button (click)="closeDialog(true)">OK</button>
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(8888);
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:8888/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:8888/users # ユーザ更新 $ curl -s -X POST -H 'Content-Type: application/json' \ -d '{"id":4,"name":"Sasaki2","email":"sasaki2@example.com"}' \ http://127.0.0.1:8888/users/4 # ユーザ削除 $ curl -s -X DELETE http://127.0.0.1:8888/users/4
REST-API サーバと非同期通信を行うために、RxJS というライブラリを利用します。
: import { FormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { MatDialogModule } from '@angular/material/dialog'; : @NgModule({ : imports: [ BrowserModule, AppRoutingModule, FormsModule, HttpClientModule, : ], :
url に指定するアドレスは適切に変更してください。サーバからではなくブラウザから見た RESTサーバのアドレスを指定します。
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
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:8888'; // 適切に変更してください
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 { 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(): voi { 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, private dialog: MatDialog ) { } ngOnInit(): void { 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"]); }); } }); } }
サーバが保持するユーザデータを、一覧表示、編集することが可能となりました。ここまでのプログラム(src)配下を angular-sample-2.zip としてダウンロードできます。