Angular10入門(環境構築〜REST APIアクセス)

はじめに

近いうちにAngularを使いそうなので学習の過程をメモしていきます。
今回は環境構築から簡単なコンポーネントの作成、REST APIアクセスまでを実践していきます。

事前学習

とりあえずYouTubeangular と検索し、トップに出てきた以下の動画で学習しました。
TypeScriptの基本からAngularの仕組みについてわかりやすく説明されているので、英語が苦でなければおすすめします。

www.youtube.com

本エントリで実施する範囲はほぼこの動画でカバーできると思いますが、APIアクセス周りについては以下の記事を参考にしました。

環境構築

Node.jsのインストール

実行環境としてNodeが必要なのでインストールします。
バージョンは2020/8/10時点で最新の v14.7.0 を用意しました。

❯ nvm install v14.7.0

Angularのインストール

npmで @angular/cli をインストールします。

❯ npm install -g @angular/cli
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
/Users/hiroki_sawano/.nvm/versions/node/v14.7.0/bin/ng -> /Users/hiroki_sawano/.nvm/versions/node/v14.7.0/lib/node_modules/@angular/cli/bin/ng

> @angular/cli@10.0.5 postinstall /Users/hiroki_sawano/.nvm/versions/node/v14.7.0/lib/node_modules/@angular/cli
> node ./bin/postinstall/script.js

? Would you like to share anonymous usage data with the Angular Team at Google under
Google’s Privacy Policy at https://policies.google.com/privacy? For more details and
how to change this setting, see http://angular.io/analytics. No
+ @angular/cli@10.0.5
added 280 packages from 206 contributors in 53.464s

バージョンは 10.0.5 です。

❯ ng --version

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/
    

Angular CLI: 10.0.5
Node: 14.7.0
OS: darwin x64

Angular: 
... 
Ivy Workspace: 

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.1000.5
@angular-devkit/core         10.0.5
@angular-devkit/schematics   10.0.5
@schematics/angular          10.0.5
@schematics/update           0.1000.5
rxjs                         6.5.5

新規アプリケーションの作成

ng newで新規アプリを作成します。
プロンプトでAngular routingはあり、スタイルシートにはSCSSを選択しました。Angular routingは本エントリで扱いませんが、次回以降で使用していく予定です。

❯ ng new my-app
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS   [ https:
//sass-lang.com/documentation/syntax#scss                ]
CREATE my-app/README.md (1023 bytes)
CREATE my-app/.editorconfig (274 bytes)
CREATE my-app/.gitignore (631 bytes)
CREATE my-app/angular.json (3654 bytes)
CREATE my-app/package.json (1249 bytes)
CREATE my-app/tsconfig.base.json (458 bytes)
CREATE my-app/tsconfig.json (426 bytes)
CREATE my-app/tslint.json (3184 bytes)
CREATE my-app/.browserslistrc (853 bytes)
CREATE my-app/karma.conf.js (1018 bytes)
CREATE my-app/tsconfig.app.json (292 bytes)
CREATE my-app/tsconfig.spec.json (338 bytes)
CREATE my-app/src/favicon.ico (948 bytes)
CREATE my-app/src/index.html (291 bytes)
CREATE my-app/src/main.ts (372 bytes)
CREATE my-app/src/polyfills.ts (2835 bytes)
CREATE my-app/src/styles.scss (80 bytes)
CREATE my-app/src/test.ts (753 bytes)
CREATE my-app/src/assets/.gitkeep (0 bytes)
CREATE my-app/src/environments/environment.prod.ts (51 bytes)
CREATE my-app/src/environments/environment.ts (662 bytes)
CREATE my-app/src/app/app-routing.module.ts (245 bytes)
CREATE my-app/src/app/app.module.ts (393 bytes)
CREATE my-app/src/app/app.component.scss (0 bytes)
CREATE my-app/src/app/app.component.html (25757 bytes)
CREATE my-app/src/app/app.component.spec.ts (1059 bytes)
CREATE my-app/src/app/app.component.ts (211 bytes)
CREATE my-app/e2e/protractor.conf.js (869 bytes)
CREATE my-app/e2e/tsconfig.json (299 bytes)
CREATE my-app/e2e/src/app.e2e-spec.ts (639 bytes)
CREATE my-app/e2e/src/app.po.ts (301 bytes)
✔ Packages installed successfully.
    Successfully initialized git.

作成された my-app ディレクトリに移動しておきます。

❯ cd my-app/

アプリケーションのビルド・起動

ng serveでアプリをビルド、起動します。 --open オプションをつけると自動でブラウザが開きます。
ng serve はファイルの変更を検知し、自動でリビルドしてくれるので、以降の作業では起動しっぱなしにしておきます。

❯ ng serve --open
Compiling @angular/core : es2015 as esm2015
Compiling @angular/animations : es2015 as esm2015
Compiling @angular/compiler/testing : es2015 as esm2015
Compiling @angular/core/testing : es2015 as esm2015
Compiling @angular/animations/browser : es2015 as esm2015
Compiling @angular/common : es2015 as esm2015
Compiling @angular/platform-browser : es2015 as esm2015
Compiling @angular/common/http : es2015 as esm2015
Compiling @angular/common/testing : es2015 as esm2015
Compiling @angular/platform-browser-dynamic : es2015 as esm2015
Compiling @angular/platform-browser/testing : es2015 as esm2015
Compiling @angular/router : es2015 as esm2015
Compiling @angular/animations/browser/testing : es2015 as esm2015
Compiling @angular/common/http/testing : es2015 as esm2015
Compiling @angular/forms : es2015 as esm2015
Compiling @angular/platform-browser/animations : es2015 as esm2015
Compiling @angular/platform-browser-dynamic/testing : es2015 as esm2015
Compiling @angular/router/testing : es2015 as esm2015

chunk {main} main.js, main.js.map (main) 60.6 kB [initial] [rendered]
chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 141 kB [initial] [rendered]
chunk {runtime} runtime.js, runtime.js.map (runtime) 6.15 kB [entry] [rendered]
chunk {styles} styles.js, styles.js.map (styles) 12.9 kB [initial] [rendered]
chunk {vendor} vendor.js, vendor.js.map (vendor) 2.65 MB [initial] [rendered]
Date: 2020-08-09T13:42:30.673Z - Hash: e069bd65d556c23a289b - Time: 18402ms
** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **
: Compiled successfully.

http://localhost:4200/ が開き、以下の初期画面が表示されました。

f:id:hiroki-sawano:20200809224454p:plain

生成されたAppコンポーネントの確認と編集

初期画面の実装は src/app/app.component.html です。このファイルを以下のように書き換えます。

<h1>{{ title }}</h1>

保存すると、自動的にコンパイルされ、変更後のページが読み込まれます。

f:id:hiroki-sawano:20200809224921p:plain

このHTMLは app.component.ts@Componentデコレータに指定されたtemplateUrlで設定されています。
{{ title }} はこのモジュールでエクスポートしてあるクラス AppComponent のプロパティ title の値を参照しています。

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'my-app';
}

@ComponentselectorコンポーネントをHTML上に埋め込む際に使用するセレクタです。
src/index.html を確認するとbody部に <app-root> が記述され、Appコンポーネントが組み込まれていることがわかります。

<body>
  <app-root></app-root>
</body>

selectorCSSセレクタなので、id属性で指定する場合は selector: '#app-root' 、 class属性で指定する場合は selector: '.app-root' となります。その時、テンプレートはそれぞれ <div id="app-root"></div><div class="app-root"></div> のようになります。

上記のHTMLをChromeデベロッパーツールで確認するとこんな感じです。

f:id:hiroki-sawano:20200809225705p:plain

スタイルは@ComponentstyleUrlsで指定されています。配列なので複数指定可能です。
AppComponent に設定されたスタイルシート app.component.scss を変更してみます。

h1 {
  color: red;
}

スタイルが適用され、フォントの色が変わりました。

f:id:hiroki-sawano:20200809230507p:plain

コンポーネントの作成

ユーザの一覧を表示するコンポーネント user-listng generateで作成します。

❯ ng g c user-list
CREATE src/app/user-list/user-list.component.scss (0 bytes)
CREATE src/app/user-list/user-list.component.html (24 bytes)
CREATE src/app/user-list/user-list.component.spec.ts (643 bytes)
CREATE src/app/user-list/user-list.component.ts (287 bytes)
UPDATE src/app/app.module.ts (485 bytes)

作成したコンポーネントAppModule@NgModuledeclarationsに自動的に追加されます。

// ...
import { UserListComponent } from './user-list/user-list.component'; // 追加

@NgModule({
  declarations: [
    AppComponent,
    UserListComponent //追加
  ],
// ...

生成されたテンプレートでは user-list works! とだけ表示されます。

<p>user-list works!</p>

コンポーネントの実装は以下の通りです。

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html',
  styleUrls: ['./user-list.component.scss']
})
export class UserListComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

}

UserListコンポーネントの表示

まずは自動生成されたコンポーネントをそのまま表示してみます。
AppComponent のテンプレートに <app-user-list> を追記します。

<h1>{{ title }}</h1>
<app-user-list></app-user-list>

user-list.component.html の内容が表示されました。

f:id:hiroki-sawano:20200809233004p:plain

ユーザ一覧の表示

Userクラスの作成

ユーザを表現するクラス User を作成します。

❯ ng g class user --type=model
CREATE src/app/user.model.spec.ts (152 bytes)
CREATE src/app/user.model.ts (22 bytes)

user.model.tsUser クラスでコンストラクタに必要なプロパティを定義します。
username のみ必須で後は任意な項目としました。

export class User {
  constructor(private username: string, private firstName?: string, private lastName?:string, private email?: string) { }
}

ユーザ一覧を返すプロパティを実装

User クラスをインポートして、適当な User オブジェクトを users で返します。

import { User } from '../user.model';
// ...

export class UserListComponent implements OnInit {
  // ...
  get users() {
    let user1 = new User('foo');
    let user2 = new User('bar', 'John', 'Doe', 'foo@example.com');
    return [user1, user2]
  }
}

Userオブジェクトをループして表示

テンプレートで*ngForを使ってユーザ一覧を表示します。

<table>
  <thead>
    <tr>
      <th>Username</th>
      <th>First Name</th>
      <th>Last Name</th>
      <th>Email</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let user of users">
      <td>{{ user.username }}</td>
      <td>{{ user.firstName }}</td>
      <td>{{ user.lastName }}</td>
      <td>{{ user.email }}</td>
    </tr>
  </tbody>
</table>

スタイルシートでは枠線を設定しておきます。

table, th, td {
  border: 1px solid black;
  border-collapse: collapse;
}

以下のように User オブジェクトの一覧が表示できました。

f:id:hiroki-sawano:20200810001857p:plain

REST APIでユーザ一覧を取得

次にユーザの一覧をREST APIで取得するように変更します。

APIの実装

APIのモックをjson-serverで用意します。

❯ npm install -g json-server

/users で2件のユーザ情報を返すように設定します。

{
  "users": [
    { "username": "foo"},
    { "username": "bar", "firstName": "John", "lastName": "Doe", "email": "foo@example.com"}
  ]
}

サーバを起動します。

❯ json-server --watch db.json 

これで、 http://localhost:3000/users にGETリクエストを投げると db.json に記述したJSONを返すようになりました。

❯ curl http://localhost:3000/users
[
  {
    "username": "foo"
  },
  {
    "username": "bar",
    "firstName": "John",
    "lastName": "Doe",
    "email": "foo@example.com"
  }
]

HttpClientModuleのインポート

HTTPリクエストを扱うため、HttpClientModuleをインポートします。

import { HttpClientModule } from '@angular/common/http'; // 追加
// ...

@NgModule({
  // ...
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule // 追加
  ],
  // ...
})
export class AppModule { }

サービスの作成

APIを叩くサービスクラスを作成します。

❯ ng g service api
CREATE src/app/api.service.spec.ts (342 bytes)
CREATE src/app/api.service.ts (132 bytes)

生成された api.service.ts に以下のコードを追加します。

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; // 追加

@Injectable({
  providedIn: 'root'
})
export class ApiService {
  constructor(private httpClient: HttpClient) { } // HttpClientをインジェクト
  fetch(){ // 追加
    return this.httpClient.get('http://localhost:3000/users');
  }
}

UserListコンポーネントからAPIを実行

最後に UserListComponent のコンストラクタで ApiService をインジェクトし、ngOnInitusers プロパティにAPIから取得したJSONを設定します。
User クラスを用いた実装は不要なので削除しました。

import { Component, OnInit } from '@angular/core';
import { ApiService } from '../api.service'; // 追加

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html',
  styleUrls: ['./user-list.component.scss']
})
export class UserListComponent implements OnInit {
  users = [] // 追加
  constructor(private apiService: ApiService) { } // サービスをインジェクト
  ngOnInit(): void {
    this.apiService.fetch().subscribe((data: any[])=>{ // 追加
      this.users = data;
    })
  }
}

これでAPIから取得したユーザ一覧を表示することができました。

f:id:hiroki-sawano:20200810173302p:plain

さいごに

ここまでの実装は以下のリポジトリに格納しておきました。

github.com

次回以降ではルーティングの設定や、マテリアルデザインの導入、Firebaseとの連携等を試していく予定です。