API Server と Angular を使用して関連テーブルのデータを表示する
Angular は動的な Web アプリを構築するためのフレームワークで、Angular JS の原則を基盤として拡張されています。CData API Server を使用すると、オンプレミスやクラウドベースのデータベースを含む80以上のデータソースに対して REST API を生成できます。この記事では、CData API Server をセットアップして QuickBooks Online データ用の OData ベースの REST API を作成し、QuickBooks Online データにライブアクセスできるシンプルなシングルページアプリケーション(SPA)を構築する手順を説明します。この SPA では、関連する QuickBooks Online テーブル(請求書と請求書明細行など)に基づいて HTML テーブルを動的に構築・表示します。この記事ではコードの大部分を解説していますが、サンプル Angular プロジェクトをダウンロードして、完全なソースコードの確認や機能のテストを行うことができます。
API Server のセットアップ
まだお持ちでない場合は、CData API Server をダウンロードしてください。API Server をインストールしたら、アプリケーションを起動し、データへの接続設定(この記事の手順では付属のサンプルデータベースを使用)を行い、SPA でアクセスしたいテーブルの REST API を作成する設定を行います。
CORS の有効化
Angular Web アプリと API Server が異なるドメインにある場合、Angular はクロスドメインリクエストを生成します。これは、Angular Web アプリからクエリを受けるサーバーで CORS(Cross-Origin Resource Sharing)を有効にする必要があることを意味します。API Server の CORS を有効にするには、API Server の SETTINGS ページの Server タブに移動します。以下の設定を調整してください:
- 「Enable cross-origin resource sharing (CORS)」のチェックボックスをオンにします。
- 「Allow all domains without '*'」のチェックボックスをオンにするか、Access-Control-Allow-Origin で接続を許可するドメインを指定します。
- Access-Control-Allow-Methods を「GET,PUT,POST,OPTIONS」に設定します。
- Access-Control-Allow-Headers を「authorization」に設定します。
- Save Changes をクリックします。
QuickBooks Online への接続
デプロイ後、API Server 管理コンソールで Settings -> Connections をクリックし、新しい接続を追加して、QuickBooks Online への接続に必要な認証値やその他の接続プロパティを設定します。
QuickBooks Online は OAuth 認証標準を使用します。OAuth では、認証ユーザーがブラウザ経由でログインする必要があります。OAuth を使用して認証するには、組み込みの OAuthClientId、OAuthClientSecret、CallbackURL を使用するか、Intuit でアプリを登録して独自の資格情報を取得できます。また、CompanyId を指定する必要があります。
OAuth の使用方法については、ヘルプドキュメントの「はじめに」の章を参照してください。

ユーザーの設定
次に、API Server 経由でデータベースデータにアクセスするためのユーザーを作成します。SETTINGS ページの Users タブでユーザーを追加・設定できます。ここでは、データを表示するだけのシンプルな SPA を作成するため、読み取り専用アクセス権を持つユーザーを作成します。 Add をクリックし、ユーザー名を入力し、Privileges で GET を選択して、Save Changes をクリックします。

スクリーンショットで確認できるように、読み取りと書き込みのアクセス権を持つユーザーは既に設定されています。この記事では、関連付けられた authtoken を使用して、読み取り専用ユーザーで API Server にアクセスします。

テーブルへのアクセス
ユーザーを作成したら、データベーステーブルへのアクセスを有効にします。テーブルを有効にするには、SETTINGS ページの Resources タブで Add Resources ボタンをクリックします。アクセスしたいデータ接続を選択し、 Next をクリックします。接続を選択した状態で、テーブル名をクリックして Next をクリックすることでリソースを有効にできます。テーブルは一度に1つずつ追加する必要があります。この例では、すべてのテーブルを有効にしました。

REST API のサンプル URL
データベースへの接続を設定し、ユーザーを作成し、API Server にリソースを追加したことで、これらのリソースに対する OData プロトコルベースの REST API に簡単にアクセスできるようになりました。以下に、テーブルとそのアクセス URL の一覧を示します。テーブルへのアクセス方法については、API Server の API ページに移動して確認できます。URL には、API Server のアドレスとポートが必要です。Angular で作業しているため、デフォルトで JSON データを返さない URL の末尾に @json パラメータを追加します。
| テーブル | URL |
|---|---|
| エンティティ(テーブル)一覧 | http://address:port/api.rsc/ |
| QBO_Invoices テーブルのメタデータ | http://address:port/api.rsc/QBO_Invoices/$metadata?@json |
| QBO_Invoices データ | http://address:port/api.rsc/QBO_Invoices |
標準の OData フィードと同様に、返されるフィールドを制限したい場合は、クエリに $select パラメータを追加できます。また、$filter、$orderby、$skip、$top などの標準 URL パラメータも使用できます。
シングルページアプリケーションの構築
API Server のセットアップが完了したので、SPA を構築していきましょう。.zip ファイルに含まれる SPA のソースファイルを順に説明し、関連するコードセクションを紹介します。いくつかのソースファイルは、angular.io の Angular チュートリアルを参考にしています。
src/index.html
これは SPA のホームページで、ソースコードの主な内容は必要な Angular ライブラリをインポートするための script 要素です。
src/main.ts
この TypeScript ファイルはアプリをブートストラップします。
src/app/app.module.ts
この TypeScript ファイルは、SPA を作成・実行するために必要なモジュールをインポートするクラスを作成します。コンポーネントとサービスの定義も含まれています。
src/app/app-routing.module.ts
この TypeScript ファイルは、SPA のコンテンツをナビゲートするために使用されるルートとパスを定義します。
src/app/app.component.css
このファイルは、Web アプリの h1 および h2 要素を変更するための CSS ルールセットを作成します。
src/app/app.component.ts
この TypeScript ファイルは SPA のコンポーネントを作成し、テンプレートを定義します。このアプリはシンプルですが、複数のルーティングやコンポーネントを含むように簡単に拡張できます。
src/app/dashboard.component.css
このファイルは、HTML の table、th、td 要素を変更するための CSS ルールセットを作成します。
src/app/dashboard.component.html
このファイルは、SPA のダッシュボードコンポーネントのレイアウトを定義します。テンプレートには、テーブルと関連テーブルを選択するドロップダウン、テーブルの外部キーを示すドロップダウン、親テーブルデータを表示する HTML テーブル、子テーブルデータを表示する別の HTML テーブルが含まれています。*ngIf ディレクティブの条件に基づいて異なるセクションが有効/無効になり、*ngFor ディレクティブを使用して API Server から返されたデータをループし、メニューとテーブルが動的に構築されます。

API Server へのすべての呼び出しと変数への値の割り当ては、DashboardComponent クラスと AppService クラスで行われます。
<div style='float:left' class="table_select">
<label>Select a Table</label>
<br>
<select [(ngModel)]="selectedTable" (change)="tableChanged()">
<option *ngFor="let sel_table of tableNames" [value]="sel_table">{{sel_table}}</option>
</select>
<br>
<div *ngIf="selectedTable">
<label>Select the Key for [{{selectedTable}}]</label>
<br>
<select [(ngModel)]="tableKey">
<option *ngFor="let sel_column of tableColumns" [value]="sel_column">{{sel_column}}</option>
</select>
<br>
</div>
</div>
<div class="subtable_select" *ngIf="selectedTable">
<label>Select a SubTable</label>
<br>
<select [(ngModel)]="selectedSubTable" (change)="subTableChanged()">
<option *ngFor="let sel_table of tableNames" [value]="sel_table">{{sel_table}}</option>
</select>
<br>
<div *ngIf="selectedSubTable">
<label>Select the Key for [{{selectedSubTable}}]</label>
<br>
<select *ngIf="selectedSubTable" [(ngModel)]="subTableKey">
<option *ngFor="let sel_column of subTableColumns" [value]="sel_column">{{sel_column}}</option>
</select>
<br>
</div>
</div>
<div *ngIf="selectedTable && tableKey && selectedSubTable && subTableKey && tableData?.length > 0" class="data_retrieve">
<br>
<h2>Click an Entry from [{{selectedTable}}] to Expand the [{{selectedSubTable}}] Entities</h2>
<table>
<tr>
<th *ngFor="let column of tableColumns">{{ column }}</th>
</tr>
<tr style='cursor:pointer' *ngFor="let row of tableData" (click)="rowClicked(row[tableKey])">
<td *ngFor="let column of tableColumns">{{ row[column] }}</td>
</tr>
</table>
</div>
<div *ngIf="selectedSubTable && subTableColumns && subTableData?.length > 0">
<br>
<hr>
<h2>Data from [{{selectedSubTable}}]</h2>
<table>
<tr>
<th *ngFor="let column of subTableColumns">{{ column }}</th>
</tr>
<tr *ngFor="let row of subTableData">
<td align=center *ngFor="let column of subTableColumns">{{ row[column] }}</td>
</tr>
</table>
</div>
src/app/app.service.ts
この TypeScript ファイルは、API Server からデータを取得するサービスを構築します。テーブルの一覧を取得する関数、特定のテーブルのカラム一覧を取得する関数、テーブルからデータを取得する関数があります。また、API Server から返されるテーブルのメタデータを表すクラスもあります。
API_Table
API Server から返されるテーブルのメタデータには、テーブルの名前、種類、URL が含まれます。ここでは name フィールドのみを使用しますが、SPA を拡張する際に他の情報が必要になる可能性があるため、オブジェクト全体を渡しています。
export class API_Table {
name: string;
kind: string;
url: string;
}
constructor()
コンストラクターでは、Http クラスのプライベートインスタンスを作成し、先ほど作成したユーザーの user/authtoken 資格情報に基づいて Authorization HTTP ヘッダーを設定します。その後、このヘッダーを HTTP リクエストに含めます。
constructor(private http: Http) {
this.headers.append('Authorization', 'Basic ' + btoa(this.userName+":"+this.authToken));
}
getTables()
この関数はテーブルの一覧を返します。一覧は、Authorization ヘッダーを含む HTTP GET リクエストを API Server のベース URL(http://localhost:8153/api.rsc)に送信して取得します。
getTables(): Promise<API_Table[]> {
return this.http.get(this.baseUrl, {headers: this.headers})
.toPromise()
.then(response => response.json().value )
.catch(this.handleError);
}
getColumns()
この関数は、tableName で指定されたテーブルのカラム一覧を返します。$metadata エンドポイントはデフォルトで XML 形式のデータを返すため、API Server から JSON データを取得するために URL に @json パラメータを渡します。JSON データを取得したら、ドリルダウンしてカラム名の一覧を取得できます。
getColumns(tableName: string): Promise<string[]> {
return this.http.get(`${this.baseUrl}/${tableName}/$metadata?@json`, {headers: this.headers})
.toPromise()
.then(response => response = response.json().items[0]["odata:cname"] )
.catch(this.handleError);
}
getTableDataByColumns(tableName:string, columnList: string)
この関数は、指定されたテーブルとカラムのデータ行を返します。URL に tableName を渡し、カラムのリスト(カンマ区切りの文字列)を $select URL パラメータの値として渡します。特定のカラムが指定されていない場合は、$select URL パラメータを使用せず、すべてのカラムをリクエストします。
getTableDataByColumns(tableName:string, columnList: string): Promise<Object[]> {
if (columnList) {
return this.http.get(`${this.baseUrl}/${tableName}/?$select=${columnList}`, {headers: this.headers})
.toPromise()
.then(response => response = response.json().value )
.catch(this.handleError);
} else {
return this.http.get(`${this.baseUrl}/${tableName}/`, {headers: this.headers})
.toPromise()
.then(response => response = response.json().value )
.catch(this.handleError);
}
}
getAllTableDataById(tableName:string, idColumn:string, idValue:string)
この関数は、指定された ID カラムと値に基づいて、指定されたテーブルのデータ行を返します。URL に tableName を渡し、ID カラムと値を使用してメインテーブルの特定のエントリに関連するデータをリクエストします。
getAllTableDataById(tableName:string, idColumn:string, idValue:string): Promise<Object[]> {
return this.http.get(`${this.baseUrl}/${tableName}(${idColumn}='${idValue}')`, {headers: this.headers})
.toPromise()
.then(response => response = JSON.parse('[' + response['_body'] + ']'))
.catch(this.handleError);
}
src/app/dashboard.component.ts
この TypeScript ファイルでは、SPA のイベントに反応する関数を定義しています。これらの関数内で AppService の関数を呼び出し、その結果を使用して SPA のさまざまな要素を表示します。これらの関数は比較的シンプルで、必要に応じて異なる変数に値を割り当てています。
ngOnInit()
この関数では、AppService の getTables 関数を呼び出します。getTables は API Server のテーブルクエリから生のデータオブジェクトを返すため、各結果の name フィールドのみを利用可能なテーブルの配列にプッシュし、オブジェクト全体はプッシュしません。
ngOnInit(): void {
this.appService
.getTables()
.then( tables => {
for (let tableObj of tables) {
this.tableNames.push( tableObj.name )
}
});
}
tableChanged()
この関数は、ユーザーが SPA のドロップダウンメニューからテーブルを選択するたびに呼び出されます。この関数は API Server を呼び出して指定されたテーブルのカラム一覧を取得し、別のドロップダウンメニューに表示します。また、選択されたテーブルのデータを取得し、HTML テーブルに表示します。
tableChanged(): void {
this.appService
.getColumns(this.selectedTable)
.then( columns => this.tableColumns = columns.sort() );
this.appService
.getTableDataByColumns(this.selectedTable, this.tableColumns)
.then( data => this.tableData = data );
}
subTableChanged()
この関数は、ユーザーがドロップダウンメニューから関連テーブルを選択するたびに呼び出されます。この関数は API Server を呼び出して指定されたテーブルのカラム一覧を取得し、別のドロップダウンメニューに表示します。
subTableChanged(): void {
this.appService
.getColumns(this.selectedSubTable)
.then( columns => this.subTableColumns = columns.sort() );
}
rowClicked(keyValue: string)
この関数は、メインテーブルのデータ行がクリックされるたびに呼び出されます。クリックされた行の ID 値(メインテーブルで選択されたカラムに基づく)をキャプチャし、選択された ID に基づいて関連テーブルからデータを取得するために API Server を呼び出します。取得したデータは HTML テーブルに表示されます。
rowClicked(keyValue: string): void {
columnList = this.selectedColumns.join(',');
this.appService
.getTableData( this.selectedTable, columnList )
.then( data => this.tableData = data );
}
シングルページアプリケーションの実行
データへの接続を設定し、SPA のソースファイルを確認したら、シングルページアプリケーションを実行する準備が整いました。SPA を実行するには、マシンに node.js と npm がインストールされている必要があります。サンプルダウンロードには、事前設定された package.json ファイルが含まれています。SPA のルートディレクトリでコマンドラインから npm install を実行して、必要なモジュールをインストールできます。SPA を起動するには、同じディレクトリで npm start を実行します。
SPA が起動すると、タイトルとテーブルを選択するドロップダウンメニューが表示されます。テーブルの一覧は API Server から取得され、API Server の設定時にリソースとして追加したすべてのテーブルが含まれています。

テーブルを選択すると、カラムのドロップダウンが表示され、テーブルとサブテーブルを関連付けるキーカラムを選択できます。

メインテーブルとキーカラムを選択したら、関連するサブテーブルを選択できます。

サブテーブルを選択すると、カラムのドロップダウンが表示され、テーブルとサブテーブルを関連付けるキーカラムを選択できます。

テーブルとカラムを選択すると、メインテーブルのデータが表示されます。HTML テーブルの行をクリックすると、クリックしたエントリに対応するサブテーブルの関連明細行が取得されます。

無料トライアルと詳細情報
動的な Web ページでデータベースデータに接続する基本的な例をご覧いただきました。API Server ページで API Server の詳細情報を確認し、API Server をダウンロードしてください。QuickBooks Online などのオンプレミスおよびクラウドベースのデータベース、アプリケーション、サービスからのライブデータを使用して、動的な Web ページの構築を始めましょう。ご質問がございましたら、CData のサポートチームがいつでもお手伝いいたします。