Angular入門

トップ > Angular入門

目次

Angular とは

チュートリアルのイメージ

このチュートリアルでは、最終的に下記の様なイメージの画面を作成します。メニューやユーザ名をクリックしてください。値までは変化しませんが、これから作成する画面のおおよそのイメージをつかむことができます。

Angular Sample Console
Dashboard

This is a sample of Angular application.

Users
IdNameEmail
1Yamadayamada@example.com
2Suzukisuzuki@example.com
3Tanakatanaka@example.com
User edit
Id1
Name
Email
Confirm

Are you sure you want to update this user?

チュートリアルで作成するソースコードは下記からダウンロードできます。

インストール

Node.js 系のパッケージ管理ツール npm を使用して、Angular の CLI (Command Line Interface) をインストールします。

Shell
$ npm install -g @angular/cli

アプリケーションを作成する

ng コマンドで新しいアプリケーションを作成します。my-app は作成するアプリケーション名となります。

Shell
$ ng new my-app

サーバを起動する

アプリケーションをビルドし、http://127.0.0.1:4200/ でアクセス可能なサーバを起動します。起動にはしばらく時間がかかります。ブラウザでアクセスすると、Angular のサンプル画面が表示されます。LISTEN する IPアドレスやポート番号を変更するには、--host, --port オプションを指定します。他のマシンからアクセスする際は、firewall-cmd などでポートの穴あけが必要な場合があります。

Shell
$ cd my-app
$ ng serve    // --host 0.0.0.0 --port 3000

HTMLやCSSを編集する

ソースを下記の様に書き換え、Hello world! を表示します。サーバが起動していれば、ソースファイルを修正すると自動的にリビルドと、ブラウザの再表示が行われます。

src/app/app.component.html
<h1>Hello world!</h1>
src/styles.css
* {
  margin: 0;
  padding: 0;
}
h1 {
  font-size: 1.4em;
}

ブラウザが自動的にリロードされ、Hello world! が表示されます。

コンポーネントを追加する

ヘッダ、メニュー、ダッシュボードなどの画面要素をコンポーネントとして追加します。g は generate の略です。

Shell
$ ng g component header
$ ng g component menu
$ ng g component dashboard

app コンポーネントの HTML を下記の様に書き換えます。

src/app/app.component.html
<app-header></app-header>
<app-menu></app-menu>
<app-dashboard></app-dashboard>

ヘッダは黒地に白文字とし、Angular のアイコンを表示します。

src/app/header/header.component.html
<div class="header">
  <img src="../favicon.ico" alt="Angular"> Angular Sample Console
</div>
src/app/header/header.component.css
.header {
  padding: 4px;
  background-color: #000;
  color: #fff;
}
.header img {
  width: 18px;
  vertical-align: middle;
}

メニューには Dashboar メニューを記述します。

src/app/menu/menu.component.html
<div class="menu">
  <ul>
  <li><a href="/">Dashboard</a></li>
  </ul>
</div>
src/app/menu/menu.component.css
.menu {
  background-color: #ccc;
}
.menu li {
  padding: 4px 8px;
  display: inline-block;
}

ダッシュボードにはタイトルを表示します。

src/app/dashboard/dashboard.component.html
<h1>Dashboard</h1>

ヘッダ、メニューバー、ダッシュボードタイトルが表示されれば成功です。

変数の値を表示する

{{変数名}} で、コンポーネントが持つ変数を表示することができます。各ソースを下記の様に修正してください。

src/app/dashboard/dashboard.component.ts
  :
export class DashboardComponent implements OnInit {
  message: string;

  constructor() {
    this.message = 'This is a sample of Angular application.';
  }
  :
src/app/dashboard/dashboard.component.html
<h1>Dashboard</h1>
<p>{{message}}</p>

Dashboard 画面にコンポーネントで定義した message 変数の値が表示されれば成功です。

ルーティングモジュールを追加する

URL によって下記の様に表示を切り替えます。表示の切り替えにはルーティングモジュールを使用します。

まず、ルーティングモジュールを追加します。

Shell
$ ng g module app-routing --flat --module=app

ユーザ一覧、ユーザ編集コンポーネントを追加します。

Shell
$ ng g component user/user-list
$ ng g component user/user-edit

各ファイルを下記の様に修正します。

src/app/app-routing.module.ts
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 { }
src/app/app.component.html
<app-header></app-header>
<app-menu></app-menu>
<div class="main">
  <router-outlet></router-outlet>
</div>
src/app/menu/menu.component.html
<div class="menu">
  <ul>
  <li><a routerLink="/">Dashboard</a></li>
  <li><a routerLink="/users">Users</a></li>
  </ul>
</div>
src/app/user/user-list/user-list.component.html
<h1>Users</h1>

Dashboard メニューをクリックすると Dashboard 画面が、Users メニューをクリックすると Users 画面(タイトルのみ)が表示されれば成功です。

リストを表示する

Users 画面にユーザ一覧を表示します。まず、User オブジェクトのクラスを定義します。

src/app/user/user.ts
export class User {
  id: number;
  name: string;
  email: string;
}

UserList コンポーネントで users リストを初期化します。

src/app/user/user-list/user-list.component.ts
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 は配列を繰り返し表示する際に使用されるディレクティブです。

src/app/user/user-list/user-list.component.html
<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>

すこし、見栄えを定義します。

src/styles.css
* {
  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 サービスを作成します。

Shell
$ ng g service user/user

app.module.ts に user サービスを追加します。

src/app/app.module.ts
  :
import { AppRoutingModule } from './/app-routing.module';
import { UserService } from './user/user.service';
import { UserListComponent } from './user/user-list/user-list.component';
  :

@NgModule({
  :
  providers: [
    UserService
  ],
  :

サービスに、初期化、一覧取得、詳細取得、設定用のメソッドを追加します。

src/app/user/user.service.ts
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;
      }
    }
  }
}
src/app/user/user-list/user-list.component.ts
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 に割り当てられたユーザ編集画面に遷移するようにします。

src/app/user/user-list/user-list.component.html
<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 を組み込みます。

src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
  :
@NgModule({
  :
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule
  ],
  :

ユーザ編集画面のコンポーネントを定義します。

src/app/user/user-edit/user-edit.component.ts
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 を下記の様に書き換えます。

src/app/user/user-edit/user-edit.component.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>

見栄えを少し追記。

src/styles.css
  :
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) をインストールします。

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

Angular Material 用の CSS ファイルを読み込みます。

src/styles.css
@import '~@angular/material/prebuilt-themes/deeppurple-amber.css';
* {
  margin: 0;
  padding: 0;
}

モーダルダイアログのサンプルとして、コンファームダイアログコンポーネントを作成してみます。

Shell
$ ng g component tools/confirm-dialog

各ファイルを次のように修正してください。

src/app/tools/confirm-dialog/confirm-dialog.component.html
<h1>{{data.title}}</h1>
<p>{{data.message}}</p>
<button (click)="closeDialog(false)">Cancel</button>
<button (click)="closeDialog(true)">OK</button>
src/app/tools/confirm-dialog/confirm-dialog.component.ts
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);
  }
}
src/app/app.module.ts
  :
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 { }
src/app/user/user-edit/user-edit.component.ts
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 としてまとめてあります。

簡単な REST-API サーバを作る

上記までの例では、ブラウザ側の JavaScript 内にダミーデータを保持し、それを書き換えるのみでしたが、ユーザ情報をサーバから REST-API で読み出し・格納できるように拡張していきます。まず、Express を用いて簡単な REST-API サーバを作成します。

Shell
$ mkdir ~/rest-test
$ cd ~/rest-test
$ npm init                        // 質問にはすべて Enter
$ npm install express
$ vi index.js
下記の例では、Access-Control-Allow-Origin を用いて他オリジンからのアクセスを許可していますが、セキュリティ上のリスクがあります。実際に使用する際には適切なオリジンを指定するなどのセキュリティ対策を十分に行ってください。
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();
});
Shell
$ node index.js

別のコンソールから動作を確認します。

Shell
# ユーザ一覧
$ 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

RxJS で非同期通信を行う

REST-API サーバと非同期通信を行うために、RxJS というライブラリを利用します。URL に指定するアドレスは適切に変更してください。ブラウザから見たサーバのアドレスを指定します。

src/app/user/user.service.ts
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);
    };
  }
}
src/app/app.module.ts
  :
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
  :
@NgModule({
  :
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    HttpClientModule,
    :
  ],
  :
src/app/user/user-list/user-list.component.ts
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;
    });
  }
}
src/app/user/user-edit/user-edit.component.ts
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 としてダウンロードできます。


Copyright (C) 2018 杜甫々
初版:2018年2月4日 最終更新:2018年2月18日
http://www.tohoho-web.com/ex/angular.html