コンポーネントライブラリ (ngx)
ngx-components は Angular アプリケーションのためのコンポーネントコレクションかつデータアクセスレイヤです。コアコンポーネントを提供するのと同様に、Angular アプリケーションから Things Cloud へのアクセスを可能にします。これを実現するために、2 つの基本的なインポートによって構成されています。
- タイトル、ナビゲータ、タブのようなすべてのコアコンポーネントを含むコア(
@c8y/ngx-components
) - @c8y/client サービスの依存性注入を可能にする AP(
@c8y/ngx-components/api
)
前提条件
新しいアプリケーションをブートストラップするために @c8y/cli を使用していない場合、はじめにパッケージをインストールする必要があります。
$ npm install @c8y/ngx-components
次に、app モジュール(例: app.module.ts)に ngx-components モジュールを追加します。
import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule as ngRouterModule } from '@angular/router';
import { CoreModule, BootstrapComponent, RouterModule } from '@c8y/ngx-components';
@NgModule({
imports: [
BrowserAnimationsModule,
RouterModule.forRoot(),
ngRouterModule.forRoot([], { enableTracing: false, useHash: true }), // 1
CoreModule.forRoot() // 2
],
bootstrap: [BootstrapComponent] // 3
})
export class AppModule {}
- Things Cloud は pushState をサポートしていないため、
useHash
ナビゲーションを true にセットしてください。 c8y-
プレフィックスが付くコンポーネントを使用するためにCoreModule
をインポートします。- ルートアプリケーションを初期化するために
<c8y-bootstrap>
コンポーネントを使用するBootstrapComponent
でアプリケーションをブートストラップします。もしくは、好きなコンポーネントをブートストラップしてそのタグをテンプレートに含めるか、指定されたコンポーネントのみを再利用できます。
Extensions points
アプリケーションを拡張し構成するために ngx-components は Extensions points と呼ばれるコアアーキテクチャの概念を提供しています。
コンテンツ投影 (CP)
この概念によりあるコンポーネントから別のコンポーネントへ内容を投影できます。例えば、別のコ ンポーネントで <c8y-title>
を設定することでページのタイトルを設定できます。<c8y-title>
タグの内容は、次にヘッダーのバーに配置されているアウトレットコンポーネントに投影されます。この概念の利点は、投影された内容にどんな内容でも配置できることです。例えば、タイトルに別のカスタムコンポーネントを投影できます。
この概念を利用した良い例として、Angular の routerLink
ディレクティブを利用して異なるコンテキストにルーティングする c8y-action-bar-item
があります。
<c8y-action-bar-item [placement]="'right'">
<a class="btn btn-link" routerLink="add">
<i class="fa fa-plus-square"></i> {{'Add' | translate}}
</a>
</c8y-action-bar-item>
上記の例は定義するコンポーネントに関係なく、ヘッダーバーにアクション項目が表示されます。コンポーネントが初期化されると項目が表示され、コンポーネントの破棄時に削除されます。
マルチプロバイダー (MP)
マルチプロバイダーにより、宣言型のアプローチでアプリケーションを拡張できます。テンプレートに定義する代わりに、HOOK
を通してすでに定義している factory を拡張します。このフックはアプリケーションの状態が変化した際に実行されます。返り値はページに挿入されます。Angular の通常の依存性注入を利用でき、結果として特定のタイプの Observable、Promise もしくは Array を返却できます。例えば、HOOK_TABS
provider に追加することで特定のタブを定義できます。
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Tab, TabFactory, _ } from '@c8y/ngx-components';
@Injectable()
export class ExampleTabFactory implements TabFactory { // 1
constructor(public router: Router) { }
get() {
const tabs: Tab[] = [];
if (this.router.url.match(/world/g)) { // 2
tabs.push({
path: 'world/awesome',
label: 'Awesome',
icon: 'angellist'
} as Tab);
}
return tabs; // 3
}
}
TabFactory
を実装している Injectable()
サービスを定義することで(1)、どのページにどのタブを表示するのかを定義できます。この例では、Angular の Router
サービスを使用することでルートの URL が world という名前を含むかどうかを確認でき(2)、 これが Awesome
というラベルが付いたタグと一致するときのみ return します(3)。これをモジュールのプロバイダーの定義に追加することで、ルート変更のたびに get()
関数がチェックされることを確認します。
@NgModule({
declarations: [
/* ... */
],
imports: [
BrowserAnimationsModule,
RouterModule.forRoot(),
ngRouterModule.forRoot([/* ... */], { enableTracing: false, useHash: true }),
CoreModule.forRoot()
],
providers: [
{ provide: HOOK_TABS, useClass: ExampleTabFactory, multi: true} // hook the ExampleTabFactory defined earlier
],
bootstrap: [BootstrapComponent]
})
export class AppModule { }
通常、コンテキストが複数のルートで共有されている、もしくはコンテンツを解決するためにより複雑なロジックが必要となる場合は、ルートおよびマルチプロバイダー内でコンテンツ投影を利用します。
例:タイトルは1つのルートに対してのみ有効 -> コンテンツ投影を利用。タブは特定の状況下で特定のルートのみ表示される必要がある -> マルチプロバイダーを利用。
以下のフックが現時点でサポートされています。
HOOK_TABS
: 特定の条件下でタブを表示させます。HOOK_NAVIGATOR_NODES
: ナビゲータノードを表示できるようにします。HOOK_ACTION
: 特定の条件で表示、もしくは有効化するべきグローバルアクションを定義できます。HOOK_BREADCRUMB
: ヘッダーバーにパンくずリストを表示するために使用します。HOOK_SEARCH
: 検索を表示するかどうかを定義できます。HOOK_ONCE_ROUTE
: ルートを定義できます。デバイス詳細画面に新しいタブを追加したいなど、コンテキストルートを使いたい場合、こちらを使用してください。その他のすべてのルートにはデフォルトの Angular のルーターを使用してください。HOOK_COMPONENTS
: 任意の種類のコンポーネントについて、そのIDを参照して動的に表示する動的コンポーネントを定義できます。このコンポーネントを使うにはc8y-dynamic-component
を使います。最も一般的な使用例は、ダッシュボードウィジェットの登録です。
サービス
サービスは ngx-components の大部分のコンポーネントで定義されています。これらのコンポーネントは Angular の依存性注入により使用できます。つまり、これらのサービスはコンポーネントのコンストラクタに注入することができ、特定の UI 要素を追加したり削除したりできます。以下の例はアラートでこの概念を使う方法を示しています。
constructor(private alert: AlertService) {
try {
// do something that might throw an exception
} catch(ex) {
this.alert.add({
text: 'Something bad happened!'
type: 'danger';
detailedData: ex;
} as Alert);
}
}
レガシープラグイン
デフォルトアプリケーション(コックピット、デバイス管理、管理)を拡張する場合、ng1.ts
というファイルが作成されます。Angular にまだ移行されておらず、AngularJS を使用しているプラグインが存在します。addImports: []
もしくは removeImports: []
プロパティによってターゲットファイルで以前行なっていたように、アプリケーションの見た目をカスタマイズするためにこれらのプラグインを追加したり削除したりできます。以下は AngularJS のターゲットファイルでデフォルトのインポートを削除している例です。
{
"name": "example",
"applications": [
{
"contextPath": "cockpit",
"addImports": [
"my-plugin/cockpit-home",
],
"removeImports": [
"core/cockpit-home"
]
}
]
}
コックピットアプリの ng1.ts
ファイルを編集することで新しい Angular フレームワークでも同様の結果が得られます。
import '@c8y/ng1-modules/core';
// [...] more imports removed for readability
import '@c8y/ng1-modules/alarmAssets/cumulocity.json';
// import '@c8y/ng1-modules/cockpit-home/cumulocity.json'; // 1
import '@c8y/ng1-modules/deviceControlMessage/cumulocity.json';
import '@c8y/ng1-modules/deviceControlRelay/cumulocity.json';
// [...] more imports removed for readability
import 'my-plugin/cumulocity.json'; // 2
上記では、単純に元のウェルカム画面プラグインのインポートを削除し(1)、カスタムで実装したものに置き換えています(2)。すべての AngularJS のプラグインは、レガシープラグインがインポートされているということを webpack に伝えるために /cumulocity.json
を追加してください。
カスタムのデフォルトでないアプリケーションでレガシープラグインを使うには、package.json ファイルに upgrade
フラグを設定し、前述と同じインポートアプローチを使用してください。
"c8y": {
"application": {
"name": "myapp",
"contextPath": "myapp",
"key": "myapp-application-key",
"upgrade": true
}
}
また、アプリケーションのモジュール定義は、これらのプラグインをサポートするために変更してください。
import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule as NgRouterModule } from '@angular/router';
import { UpgradeModule as NgUpgradeModule } from '@angular/upgrade/static';
import { CoreModule, RouterModule } from '@c8y/ngx-components';
import { UpgradeModule, HybridAppModule, UPGRADE_ROUTES } from '@c8y/ngx-components/upgrade';
import { AssetsNavigatorModule } from '@c8y/ngx-components/assets-navigator';
@NgModule({
imports: [
BrowserAnimationsModule,
RouterModule.forRoot(),
NgRouterModule.forRoot([
...UPGRADE_ROUTES,
], { enableTracing: false, useHash: true }),
CoreModule.forRoot(),
AssetsNavigatorModule,
NgUpgradeModule,
// Upgrade module must be the last
UpgradeModule
]
})
export class AppModule extends HybridAppModule {
constructor(protected upgrade: NgUpgradeModule) {
super();
}
}
これにより、AngularJS と Angular のプラグイン、もしくはモジュールを使用できるようになるハイブリッドモードでアプリケーションを開始することができます。
どの extention point がサポートされているかや、どの概念が特定のシナリオに使われるかを判断するために、以下のセクションですべてのサポートされているコンポーネントの概要が示されており、どの場合に何を使うべきかが説明されています。
Things Cloud へのデータアクセス
CommonModule
は DataModule
をエクスポートしています。これは、@c8y/clientを抽象化したものであり、Angular の依存性注入と共にクライアントのサービスで使用することができます。つまり、CommonModule
または DataModule
がインポートされているモジュールでは、Things Cloud のデータへのアクセスは依存性注入により可能になります。
import { Component } from '@angular/core';
import { AlarmService } from '@c8y/client'; // 1
@Component({selector: 'app-alerts', template: ''})
export class AlarmComponent {
constructor(private alarmService: AlarmService) {} // 2
async getAllAlarms() {
const alarms = await this.alarmService.list(); // 3
return alarms.data;
}
}
- @c8y/clientパッケージからサービスをインポートする
- サービスを依存性注入する
- Things Cloud からデータをリクエストするためにサービスを使用する
Clientライブラリ
@c8y/client は Things Cloud API のためのアイソモーフィックな(node とブラウザ用)JavaScript クライアントです。
インストール
npm install @c8y/client
使用方法
Things Cloud REST API から一覧データを取得する場合は client.<endpoint>.list()
、詳細情報を取得する場合は client.<endpoint>.list()
を使用してください。これらのメソッドは常に promise を返します。observable を取得するには、list$
もしくは detail$
を使用してください。
promise で詳細と一覧情報を取得する(pull)
メソッド | 詳細 | パラメータ | 戻り値 |
---|---|---|---|
detail(entityOrId) |
特定のエンティティの詳細データをリクエストする | entityOrId: string | number | IIdentified : id もしくは、数値または文字列としての id を含むオブジェクト |
Promise<IResult<TData>> : IResult にラッピングされている Promise としてのリスト。IResultList はデータとレスポンスを含む |
list(filter) |
任意のフィルタを用いて一覧データをリクエストする | filter:object :(任意)クエリ結果のページングを行うためのフィルタ。Inventory > クエリ言語参照 |
Promise<IResultList<TData>> : IResultList にラッピングされている Promise としてのリスト。IResultList はデータ、レスポンス、ページングの情報を含む |
-
detail
を用いて inventory の1つの managedObject の詳細を受け取る例:const managedObjId: number = 1; (async () => { const {data, res} = await client.inventory.detail(managedObjId); })();
-
list
を使ってinventoryの1つの managedObject のリストを受け取る例:const filter: object = { pageSize: 100, withTotalPages: true }; (async () => { const {data, res, paging} = await client.inventory.list(filter); })();
Fetch APIを使用してマイクロサービスへアクセスする
client ライブラリは内部的に Fetch API を用いています。コア機能にアクセスすることにより、任意のリソースに対して認証された要求をすることができます。スタンドアロンでは core.client.fetch(url, options)
を使用し、Angular では @c8y/ngx-components/data
の FetchClient
をインジェクトするのみです。
constructor(private fetchClient: FetchClient) {} // di
async getData() {
const options: IFetchOptions = {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
};
const response = await fetchClient.fetch('/service/my-service', options); // Fetch API Response
}
content type が正しく設定されていれば、すべてのフェッチレスポンスは JSON へパース可能です。フェッチレスポンスの処理についての詳細は MDN の文書 をご覧ください。
認証方法
現時点で、Things Cloud には 2 種類の認証方法があります。
- Basic Auth: 各リクエストの認証ヘッダーに挿入されます。
- OAuth: クライアントは認証ヘッダーの内容は知りません。ヘッダーはCookieに設定されます。
すぐに使い始めることができるように、@c8y/client は常に Basic 認証を利用し、直接ログインを検証する簡易の静的関数を提供しています。
await Client.authenticate({ tenant, user, password }), url);
この関数は内部的にクライアントインスタンスを作成し、API にアクセスして与えられた認証情報が正しいかどうかを検証します。場合によっては、より詳細な認証方法を使用してください。例えば、ユーザーがどの認証方法を使用するか不明な場合があります。この場合、クライアントのインスタンスを作成し、認証情報を渡してください。
const baseUrl = 'https://acme.je1.thingscloud.ntt.com/';
const client = new Client(new CookieAuth(), baseUrl); // use here `new BasicAuth()` to switch to Basic Auth
try {
const { data, paging, res } = await client.user.currentUser();
console.log('Login with cookie successful');
} catch(ex) {
console.log('Login failed: ', ex)
}
observable で詳細データと一覧データをサブスクライブする (push)
detail$
と list$
関数により、変更の度に値を放出するリアルタイムチャンネルを購読できます。
メソッド | 詳細 | パラメータ | 戻り値 |
---|---|---|---|
detail$(entityOrId, options) |
一つのエンティティの詳細データの observable を返却する | entityOrId: string | number | IIdentified :id もしくは数値または文字列としての id を含むオブジェクトoptions: IObservableOptions : (任意)observable を定義するための設定用オブジェクト |
Observable<TData>> : 購買可能な observable としてのリスト |
list$(filter, options) |
エンティティのリストの observable を返却する。 | filter: object : (任意)クエリ結果のページングを行うためのフィルタ。 Inventory > クエリ言語参照options: IObservableOptions :(任意)observable を定義するための設定用オブジェクト |
ObservableList<TData>> : 購買可能な observable としてのリスト |
-
detail$
を利用して inventory の1つの managedObject の詳細データを受け取る例:const managedObjId: number = 1; const detail$ = client.inventory.detail$(managedObjId); detail$.subscribe((data) => console.log(data));
-
list$
を利用して inventory の1つの managedObject の一覧データを受け取る例:const list$ = client.inventory.list$(); list$.subscribe((data) => console.log(data));
observable は以下のデフォルトプロパティのように、IObservableOptions
を追加することで設定できます。
{
hot: true, // true = 1つのネットワークリクエストを共有する
realtime: false, // true = リアルタイムに変更を検知する
pagingStrategy: PagingStrategy.PROGRESSIVE, // ALL = 全てのページが読み込む
// NONE = 現在のページのみ読み込む
// PROGRESSIVE = more() でページを読み込む
realtimeAction: RealtimeAction.FULL, // FULL = 全てのCRUDリアルタイムアクションを使用する
pagingDelay: 0 // 次ページの読み込みをxミリ秒遅らせる
realtimeFilter: undefined // オプションの追加フィルタ
}
例
以下にいくつかの例を示します。Angular での client の実装についてのより詳細な情報は@c8y/cli をご覧いただき、 new
コマンドでサンプルアプリケーションの作成を試してみてください。
inventory からリストのデータをリクエストする:
import { Client } from '@c8y/client';
const baseUrl = 'https://demos.je1.thingscloud.ntt.com/';
const tenant = 'demos';
const user = 'user';
const password = 'pw';
(async () => {
const client = await Client.authenticate({
tenant,
user,
password
}, baseUrl);
const { data, paging } = await client.inventory.list();
// data = first page of inventory
const nextPage = await paging.next();
// nextPage.data = second page of inventory
})();
inventory エンドポイントの observable を取得する:
import { Client } from '@c8y/client';
const baseUrl = 'https://demos.je1.thingscloud.ntt.com/';
const tenant = 'demos';
const user = 'user';
const password = 'pw';
(async () => {
const client = await Client.authenticate({
tenant,
user,
password
}, baseUrl);
client.inventory.list$().subscribe((data) => {
// request inventory data via fetch and adds realtime if data changes
console.log(data);
});
})();
リアルタイムの使用:
// realtime event
const subscription = client.realtime.subscribe('/alarms/*', (data) => {
console.log(data); // logs all alarm CRUD changes
});
client.realtime.unsubscribe(subscription);
// realtime observable
const observable$ = client.realtime.observable('/alarms/*');
const observableSubscription = observable$.subscribe((data) => {
console.log(data)); // logs all alarm CRUD changes
});
observableSubscription.unsubscribe();
node.jsで認証
コンストラクタ new Client([...])
は、API からデータを要求するための新しいクライアントを初期化します。Client.authenticate([...])
とは異なり、テナント名が必要で、ログインが正しいかは検証しません。このコンストラクタは node.js でマイクロサービスを開発する際に便利です。
const auth = new BasicAuth({
user: 'youruser',
password: 'yourpassword',
tenant: 'acme'
});
const baseUrl = 'https://acme.je1.thingscloud.ntt.com/';
const client = new Client(auth, baseUrl);
(async () => {
const { data, paging, res }); = await client.inventory.list({ pageSize: 100 });
})();
アプリケーション ライブラリ
アプリケーションパッケージ (@c8y/apps
) は、Web SDK用にサンプルアプリケーションを提供しています。
前提条件
c8y/apps
パッケージを利用するには、@c8y/cliをインストールする必要があります。インストール方法は、ドキュメントの指示に従ってください。
インストール後、以下コマンドを実行できます。
$ c8ycli new [your-app-name] [example-name]
例えば、my-app
という名前のチュートリアル アプリケーションを生成するには、以下のコマンドを実行します。
$ c8ycli new my-app tutorial
@c8y/apps
に含まれているアプリケーション
以下の表は、現時点でサポートしているアプリケーションの一覧です。
名前 | 詳細 |
---|---|
application | 新しいアプリケーションを迅速にブートストラップするための空のアプリケーションです。アプリケーション生成時に[example-name] を指定しない場合、デフォルトでこのアプリケーションが生成されます。 |
tutorial | @c8y/ngx-componentsの概念の多くを集約済みのアプリケーションです。コード例を確認するには、こちらを使用してください。 |
cockpit | コックピットのデフォルトアプリケーションです。既存のコックピット アプリケーションを拡張する際に使用してください。 |
devicemanagement | デバイス管理のデフォルトアプリケーションです。既存のデバイス管理アプリケーションを拡張する際に使用してください。 |
administration | 管理のデフォルトアプリケーションです。既存の管理アプリケーションを拡張する際に使用してください。 |