コンポーネントライブラリ (ngx)

ngx-components は Angular アプリケーションのためのコンポーネントコレクションかつデータアクセスレイヤです。コアコンポーネントを提供するのと同様に、Angular アプリケーションから Things Cloud へのアクセスを可能にします。これを実現するために、2 つの基本的なインポートによって構成されています。

備考
すべてのモジュールとコンポーネントの完全な文書は こちら をご覧ください。

前提条件

新しいアプリケーションをブートストラップするために @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 {}

  1. Things Cloud は pushState をサポートしていないため、useHash ナビゲーションを true にセットしてください。
  2. c8y- プレフィックスが付くコンポーネントを使用するために CoreModule をインポートします。
  3. ルートアプリケーションを初期化するために <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つのルートに対してのみ有効 -> コンテンツ投影を利用。タブは特定の状況下で特定のルートのみ表示される必要がある -> マルチプロバイダーを利用。
以下のフックが現時点でサポートされています。

サービス

サービスは 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 へのデータアクセス

CommonModuleDataModule をエクスポートしています。これは、@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;
  }
}
  1. @c8y/clientパッケージからサービスをインポートする
  2. サービスを依存性注入する
  3. Things Cloud からデータをリクエストするためにサービスを使用する
備考
利用可能なサービスとデータのフィルタ方法、選択方法に関する詳細情報は @c8y/clientをご覧ください。

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 はデータ、レスポンス、ページングの情報を含む

Fetch APIを使用してマイクロサービスへアクセスする

client ライブラリは内部的に Fetch API を用いています。コア機能にアクセスすることにより、任意のリソースに対して認証された要求をすることができます。スタンドアロンでは core.client.fetch(url, options) を使用し、Angular では @c8y/ngx-components/dataFetchClient をインジェクトするのみです。

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 種類の認証方法があります。

すぐに使い始めることができるように、@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 としてのリスト

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 管理のデフォルトアプリケーションです。既存の管理アプリケーションを拡張する際に使用してください。