このセクションでは、Web SDKの一般的な使い方レシピをリストアップしています。ここでは以下が必要となります。
Angular のコンポーネント、サービス、モジュールについて基本的な理解があること
アプリケーションのスキャフォールドの仕方と@c8y/cli を使ってアプリケーションを実行する方法を理解していること
@c8y/ngx-components の Extension points の概念について基本的な理解があること
備考
すべてのレシピは、Angular 向け Web SDK の特定のバージョンで書かれています。バージョンはレシピの上部に示されています。レシピで示されている機能の中には利用可能でないものもあるため、古いバージョンでの使用はお勧めしません。レシピよりも新しいバージョンを使用する場合、名前やインポートするものに変更があるかもしれません。概念的な改訂がある場合はレシピを更新しますが、小さな変更での更新はありません。すべてのコンセプトの最新の例を見るには、c8ycli new my-app tutorial
でチュートリアルアプリケーションを確認してください。
ダッシュボードにカスタムウィジェットを追加する
バージョン: 1009.0.18 | パッケージ: @c8y/cli, @c8y/apps, @c8y/ngx-components
Things Cloudで提供しているウィジェットが要件を満たさない場合、カスタムウィジェットを作成しダッシュボードに追加できます。
典型的なダッシュボードは以下のようになり、さまざまなウィジェットが表示されます。
ここでは HOOK_COMPONENTS
を用いてダッシュボードにカスタムウィジェットを追加する方法を示しています。
1. サンプルアプリケーションを初期化する
まずはじめに、ダッシュボードを表示するアプリケーションが必要です。このために、c8ycli
を使用して新しいコックピットのアプリケーションを作成します。
c8ycli new my - cockpit cockpit - a @ c8y / apps @ 1009.0 .18
次に、依存関係をインストールします。新しく作成されたフォルダに移動し、npm install
を実行してください。
備考
c8ycli new
コマンドには、スキャフォールディングに使用するパッケージを定義する
-a
フラグがあります。このようにして、スキャフォールディングとするアプリケーションのバージョンを定義することも可能です。例えば、以下の通りです。
c8ycli new my-cockpit cockpit -a @c8y/apps@1009.0.18
は、アプリケーションのバージョン 10.9.0.18
をスキャフォールディングにします。
c8ycli new my-cockpit cockpit -a @c8y/apps@latest
は、アプリケーションの最新公式リリースをスキャフォールディングにします。-a
フラグを付けずに使用する場合と同じです。
c8ycli new my-cockpit cockpit -a @c8y/apps@next
は、アプリケーションの最新ベータリリースをスキャフォールディングにします。
ウィジェットは通常2つの部分で構成されます。
設定(Configuration): ユーザーがダッシュボードにウィジェットを追加する際に表示されるコンポーネント
ウィジェット(Widget): ダッシュボードに追加された時に表示されるコンポーネント
そのため2つのコンポーネントを作成する必要があります。
はじめに、demo-widget.component.ts
を作成します。
import { Component , Input } from '@angular/core' ;
@ Component ({
selector : 'c8y-widget-demo' ,
template : ` <p class="text">{{config?.text || 'No text'}}</p> ` ,
styles : [ ` .text { transform: scaleX(-1); font-size: 3em ;} ` ]
})
export class WidgetDemo {
@ Input () config ;
}
このコンポーネントは、CSSを介して、垂直に鏡写しされたテキストが表示します。他のAngularコンポーネントでできることは何でも行うことができます。
次のように定義されている demo-widget-config.component.ts
から設定を渡すために config
の入力が必要です。
import { Component , Input } from '@angular/core' ;
@ Component ({
selector : 'c8y-widget-config-demo' ,
template : ` <div class="form-group">
<c8y-form-group>
<label translate>Text</label>
<textarea style="width:100%" [(ngModel)]="config.text"></textarea>
</c8y-form-group>
</div> `
})
export class WidgetConfigDemo {
@ Input () config : any = {};
}
再び、ウィジェットに渡したいシリアル化可能な設定で埋めることのできる config オブジェクトを追加する必要があります。
ウィジェット設定の検証を有効にするには、@Component
デコレータに次のオプションを追加する必要があります。
import { ControlContainer , NgForm } from "@angular/forms" ;
@ Component ({
...
viewProviders : [{ provide : ControlContainer , useExisting : NgForm }]
})
上記の例と組み合わせると、設定検証を有効にしたdemo-widget-config.component.ts
コンポーネントは、次のようになります。
import { Component , Input } from '@angular/core' ;
import { ControlContainer , NgForm } from "@angular/forms" ;
@ Component ({
selector : 'c8y-widget-config-demo' ,
template : ` <div class="form-group">
<c8y-form-group>
<label translate>Text</label>
<textarea style="width:100%" [(ngModel)]="config.text" name="text"></textarea>
</c8y-form-group>
</div> ` ,
viewProviders : [{ provide : ControlContainer , useExisting : NgForm }]
})
export class WidgetConfigDemo {
@ Input () config : any = {};
}
3. アプリケーションにウィジェットを追加する
ウィジェットを追加するには、HOOK_COMPONENTS
を使用しentryComponent
として作成したコンポーネントを定義する必要があります。
そのためには、app.module.ts
に以下を追加します。
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 { DashboardUpgradeModule , UpgradeModule , HybridAppModule , UPGRADE_ROUTES } from '@c8y/ngx-components/upgrade' ;
// --- 8< 変更箇所 ----
import { CoreModule , RouterModule , HOOK_COMPONENTS } from '@c8y/ngx-components' ;
// --- >8 ----
import { AssetsNavigatorModule } from '@c8y/ngx-components/assets-navigator' ;
import { CockpitDashboardModule , ReportDashboardModule } from '@c8y/ngx-components/context-dashboard' ;
import { ReportsModule } from '@c8y/ngx-components/reports' ;
import { SensorPhoneModule } from '@c8y/ngx-components/sensor-phone' ;
import { BinaryFileDownloadModule } from '@c8y/ngx-components/binary-file-download' ;
// --- 8< 追加箇所 ----
import { WidgetDemo } from './demo-widget.component' ;
import { WidgetConfigDemo } from './demo-widget-config.component' ;
// --- >8 ----
@ NgModule ({
imports : [
// Upgrade module must be the first
UpgradeModule ,
BrowserAnimationsModule ,
RouterModule .forRoot (),
NgRouterModule .forRoot ([...UPGRADE_ROUTES ], { enableTracing : false , useHash : true }),
CoreModule .forRoot (),
AssetsNavigatorModule ,
ReportsModule ,
NgUpgradeModule ,
DashboardUpgradeModule ,
CockpitDashboardModule ,
SensorPhoneModule ,
ReportDashboardModule ,
BinaryFileDownloadModule
],
// --- 8< 追加箇所 ----
declarations : [WidgetDemo , WidgetConfigDemo ], // 1.
entryComponents : [WidgetDemo , WidgetConfigDemo ],
providers : [{
provide : HOOK_COMPONENTS , // 2.
multi : true ,
useValue : [
{
id : 'acme.text.widget' , // 3.
label : 'Text widget' ,
description : 'Can display a text' ,
component : WidgetDemo , // 4.
configComponent : WidgetConfigDemo ,
}
]
}],
// --- >8 ----
})
export class AppModule extends HybridAppModule {
constructor (protected upgrade : NgUpgradeModule ) {
super ();
}
}
上記の数字の説明は以下の通りです。
エントリーコンポーネントとしてコンポーネントを定義し、このモジュールによってアクセスできるように宣言します。
HOOK_COMPONENTS
と共にマルチプロバイダーフックを追加します。このフックはアプリケーションによって収集され、設定した値に基づいてウィジェットに追加します。
インベントリに保存されているデータを特定するため、IDは一意である必要があります。ラベルと詳細記述はタイトルとウィジェットのドロップダウンとして表示されます。
この部分は、すでに定義してあるコンポーネントとウィジェットに関連付けるようフックに指示します。
npm start
でアプリケーションを開始すると、ダッシュボードにカスタムウィジェットが表示されているはずです。
ダッシュボードに追加されると、ウィジェットは以下のように表示されます。
Jestベースのユニットテストを追加する
バージョン: 1013.0.0 | パッケージ: @c8y/cli, @c8y/apps, @c8y/ngx-components
ユニットテストはすべての開発プロセスにおいて不可欠な要素です。
バージョン 10.13.0.0 以降、すべての新しい c8ycli
スキャフォールディングアプリケーションには、ユニットテストフレームワーク Jest がデフォルトで含まれています。
このチュートリアルでは、最初のユニットテストを書いて検証する方法を紹介します。
1. サンプルアプリケーションを初期化する
例として、空のデフォルトのアプリケーションが必要です。
c8ycli new my - app application - a @ c8y / apps @ 1013.0 .62
ただし、どのようなアプリケーションでも、同じようにユニットテストをサポートします。次に、すべての依存関係をインストールする必要があります。
備考
c8ycli new
コマンドには、スキャフォールディングに使用するパッケージを定義する
-a
フラグがあります。このようにして、スキャフォールディングとするアプリケーションのバージョンを定義することも可能です。例えば、以下の通りです。
c8cycli new my-cockpit cockpit -a @c8y/apps@1013.0.62
はバージョン 1013.0.62
のアプリケーションをスキャフォールディングします。
c8ycli new my-cockpit cockpit -a @c8y/apps@latest
はアプリケーションの最新公式リリースをスキャフォールディングします。a
フラグを付けずに使用する場合と同じです。
c8ycli new my-cockpit cockpit -a @c8y/apps@next
は、アプリケーションの最新ベータリリースをスキャフォールディングにします。
2. コンポーネントを追加する
何かをテストするためには、まず検証可能なコンポーネントが必要です。そのため、test.component.ts
という名前の新しいファイルを追加します。
import { Component , OnInit } from '@angular/core' ;
@ Component ({
selector : 'app-test' ,
template : ` <h1>Hello world</h1> `
})
export class TestComponent implements OnInit {
constructor () { }
ngOnInit (): void { }
}
新しく作成したコンポーネントを app.module.ts
の宣言文に追加します。
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' ;
// --- 8< 変更箇所 ----
import { TestComponent } from # # # 2. Adding a component
ngRouterModule .forRoot ([], { enableTracing : false , useHash : true }),
CoreModule .forRoot ()
],
bootstrap : [BootstrapComponent ],
// --- 8< 変更箇所----
declarations : [
TestComponent
]
// --- >8 ----
})
export class AppModule {}
サンプルコンポーネントをモジュールに追加した後、コンポーネントをテストする準備ができました。
2. テストコンポーネント用のユニットテストを追加する
テストファイルの拡張子は .spec.ts
です。
リポジトリには、app.module.spec.ts
というサンプルのspecファイルがあります。
このspecファイル名を test.component.spec.ts
に変更して、内容を次のように調整してください。
import { ComponentFixture , TestBed } from '@angular/core/testing' ;
import { AppModule } from './app.module' ;
import { TestComponent } from './test.component' ;
describe ('Test component test' , () => {
let fixture : ComponentFixture < TestComponent > ;
beforeEach (() => {
TestBed .configureTestingModule ({
imports : [AppModule ]
});
fixture = TestBed .createComponent (TestComponent );
});
test ('should be defined' , () => {
expect (fixture ).toBeDefined ();
});
});
これは最初のテストファイルです。
Angularのテストモジュールを設定し、TestComponent
が定義できるかどうかをチェックします。
Angularのテストサポートについては、Angular webサイト で詳細を確認できます。
テストを開始するには、コマンドラインから npm test
を実行します。
これにより、package.json
にある定義済みのスクリプトが実行され、Jestが起動します。
以下のようなテスト結果が表示されるはずです。
PASS ./test.component.spec.ts (32.071 s)
Test component test
✓ should be defined (123 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 32.858 s
テストが「PASS」と表示されれば、すべてがうまくいき、最初のコンポーネントテストは成功したことになります。
これで、より詳細なテストケースを追加してコンポーネントが意図した通りに動作することを確認することができます。
3. スナップショットテストを使用して、コンポーネントテンプレートを検証する
このセクションでは、コンポーネントテンプレートを検証するための他の方法についての追加情報を提供します。
Karmaの代わりにJestを使用するのは、いわゆるスナップショットテストを使用するためのオプションが付属しているためです。
スナップショットテストは、すべての結果を定義することなく、テストの結果を検証することができます。
Jestの関数 toMatchSnapshot()
は、初回実行時のテストのスナップショットを含むファイルを作成します。
test.component.spec.ts
ファイルに以下を追加して、スナップショットテストを使用する別のテストを作成し、 TestComponent
のテンプレートを検証します。
test ("should show a title tag" , () => {
expect (fixture .debugElement .nativeElement ).toMatchSnapshot ();
});
npm test
を実行します。その結果、スナップショットが書き込まれたことが表示されます。
PASS ./test.component.spec.ts
Test component test
✓ should be defined (94 ms)
✓ should show a title tag (29 ms)
› 1 snapshot written.
Snapshot Summary
› 1 snapshot written from 1 test suite.
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 1 written, 1 total
Time: 5.154 s
このスナップショットは新しく作成されたフォルダ ./__snapshot__
にあり、確認することができます。
テンプレートが変更されると、テストは失敗するので、npm test -- -u
でテストを上書きする必要があります。
この動作は test.component.ts
ファイルでテンプレートを変更することでテストできます。
備考
このスナップショットをコードと一緒にコミットするのが一般的なやり方です。
結論
このチュートリアルでは、c8cycli
コマンドを使用して新しくスキャフォールディングされたアプリケーションに、テストを追加する方法を紹介しました。
高度なスナップショットテストには、テンプレートをすばやく検証するためのオプションがあります。
コンテキストルートを使用して詳細ビューにタブを追加する
バージョン: 1009.0.18 | パッケージ: @c8y/cli, @c8y/apps, @c8y/ngx-components
デバイスやグループなどの詳細画面でユーザーに追加の情報を表示したい、というのは一般的なユースケースです。
このレシピでは、デバイス詳細画面に新しいタブを追加する方法を説明します。
Angular向けWeb SDKでは、特定のコンテキストに対してのビューを提供するため、このような画面は ViewContext
と呼ばれます。コンテキストビューにはいくつか種類があります(例えば、Device
、 Group
、 User
、 Application
、Tenant
など)。ハッシュナビゲーションで特定の Route
でナビゲートすることで、コンテキストビューにアクセスできます。例えば、apps/cockpit/#/device/1234
へアクセスした場合、アプリケーションは 1234
のIDを持つデバイスを解決しようとします。
詳細ビューは/info
と呼ばれる別のルートから参照されている上記のスクリーンショットの 情報 タブにあるように、通常いくつかの Tabs
を表示していますが、情報を表示するためにデバイスのコンテキストを再利用しています。
以下では、apps/cockpit/#/device/:id/hello
からアクセスできるこのビューに新しいタブを追加する方法を紹介します。
1. サンプルアプリケーションを初期化する
はじめに、コンテキストルートをサポートするアプリケーションが必要です。
このために、c8cycli
を使用して新しいコックピットアプリケーションを作成します。
c8ycli new my - cockpit cockpit - a @ c8y / apps @ 1009.0 .18
次に、すべての依存関係をインストールする必要があります。新しいフォルダに移動して、npm install
を実行します。
備考
c8y cli new
コマンドには、スキャフォールディングに使用するパッケージを定義する
-a
フラグがあります。このようにして、スキャフォールディングとするアプリケーションのバージョンを定義することも可能です。例えば、以下の通りです。
c8ycli new my-cockpit cockpit -a @c8y/apps@1009.0.18
は、アプリケーションのバージョン10.9.0.18
をスキャフォールディングにします。
c8ycli new my-cockpit cockpit -a @c8y/apps@latest
は、アプリケーションの最新公式リリースをスキャフォールディングにします。-a
フラグを付けずに使用する場合と同じです。
c8ycli new my-cockpit cockpit -a @c8y/apps@next
は、アプリケーションの最新ベータリリースをスキャフォールディングにします。
2. 新しいROUTE_HOOK_ONCEを追加する
フックの概念により既存のコードに追加できます。この例では、device/:id
という既存のルートにいわゆるチャイルドルート(Angularによる)を追加します。
これを達成するために、app.module.ts
に以下のコードを追加します。
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' ;
// ---- 8< 変更箇所 ----
import { CoreModule , RouterModule , HOOK_ONCE_ROUTE , ViewContext } from '@c8y/ngx-components' ;
// ---- >8 ----
import { DashboardUpgradeModule , UpgradeModule , HybridAppModule , UPGRADE_ROUTES } from '@c8y/ngx-components/upgrade' ;
import { AssetsNavigatorModule } from '@c8y/ngx-components/assets-navigator' ;
import { CockpitDashboardModule , ReportDashboardModule } from '@c8y/ngx-components/context-dashboard' ;
import { ReportsModule } from '@c8y/ngx-components/reports' ;
import { SensorPhoneModule } from '@c8y/ngx-components/sensor-phone' ;
import { BinaryFileDownloadModule } from '@c8y/ngx-components/binary-file-download' ;
@ NgModule ({
imports : [
// Upgrade module must be the first
UpgradeModule ,
BrowserAnimationsModule ,
RouterModule .forRoot (),
NgRouterModule .forRoot ([...UPGRADE_ROUTES ], { enableTracing : false , useHash : true }),
CoreModule .forRoot (),
AssetsNavigatorModule ,
ReportsModule ,
NgUpgradeModule ,
DashboardUpgradeModule ,
CockpitDashboardModule ,
SensorPhoneModule ,
ReportDashboardModule ,
BinaryFileDownloadModule
],
// ---- 8< 追加箇所 ----
providers : [{
provide : HOOK_ONCE_ROUTE , // 1.
useValue : [{ // 2.
context : ViewContext .Device , // 3.
path : 'hello' , // 4.
component : HelloComponent , // 5.
label : 'hello' , // 6.
priority : 100 ,
icon : 'rocket'
}],
multi : true
}]
// ---- >8 ----
})
export class AppModule extends HybridAppModule {
constructor (protected upgrade : NgUpgradeModule ) {
super ();
}
}
上記の数字の説明は以下の通りです。
マルチプロバイダーフックの HOOK_ROUTE_ONCE
を提供します。これは現在のルートの設定を拡張するためにアプリケーションに指示します。
ルートフックの定義に値を使用することを指定します。例えば、ルートを非同期に解決したい場合、クラスを使用することもできます。
ルートのコンテキストを定義します。定義するには ViewContext
enum を使用する必要があります。この例では、デバイスのコンテキストを拡張します。
表示されるパスです。コンテキストパスに追加されます。この例では、完全パスは以下になります。device/:id/hello
パスがユーザーによって入力されたらどのコンポーネントを表示するかを定義します。
タブがどのように表示されるかを定義するのは label
かつ icon
プロパティです。priority
はどの位置に表示するべきかを定義します。
備考
HOOK_ONCE_ROUTE はAngular Routeの型を継承します。すべてのプロパティはここで再利用されます。
この一連の設定の後、ルートは登録されますが HelloComponent
はまだ存在しないため、アプリケーションはコンパイルに失敗します。次のセクションで HelloComponent
を作成します。
3. コンテキストデータを表示するコンポーネントを追加する
HelloComponent
は、デバイスの詳細を表示したい場合があるでしょう。
これを行うには、それが開かれたコンテキストに関する情報が必要です。
コンテキストルートは、デバイスを前もって解決してくれます。
また、親ルートで直接アクセスすることができます。
hello.component.ts
という名前のファイルを作成します。
import { Component } from '@angular/core' ;
import { ActivatedRoute } from '@angular/router' ;
@ Component ({
selector : 'app-hello' ,
template : `
<c8y-title>world</c8y-title>
<pre>
<code>
{{route.snapshot.parent.data.contextData | json}}
</code>
</pre>
`
})
export class HelloComponent {
constructor (public route : ActivatedRoute ) {}
}
このコンポーネントは ActivatedRoute
を注入し、その親データにアクセスします。
ここがキーとなるポイントです。親コンテキストルートはすでにデバイスのデータを解決しているので、このコンポーネントは常に現在のデバイスの詳細データを表示します。
これを app.module.ts
の entryComponents
に追加することでアプリケーションをコンパイルすることができます。
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 , HOOK_ONCE_ROUTE , ViewContext } from '@c8y/ngx-components' ;
import { DashboardUpgradeModule , UpgradeModule , HybridAppModule , UPGRADE_ROUTES } from '@c8y/ngx-components/upgrade' ;
import { AssetsNavigatorModule } from '@c8y/ngx-components/assets-navigator' ;
import { CockpitDashboardModule , ReportDashboardModule } from '@c8y/ngx-components/context-dashboard' ;
import { ReportsModule } from '@c8y/ngx-components/reports' ;
import { SensorPhoneModule } from '@c8y/ngx-components/sensor-phone' ;
import { BinaryFileDownloadModule } from '@c8y/ngx-components/binary-file-download' ;
// ---- 8< 追加箇所 ----
import { HelloComponent } from './hello.component' ;
// ---- >8 ----
@ NgModule ({
imports : [
// Upgrade module must be the first
UpgradeModule ,
BrowserAnimationsModule ,
RouterModule .forRoot (),
NgRouterModule .forRoot ([...UPGRADE_ROUTES ], { enableTracing : false , useHash : true }),
CoreModule .forRoot (),
AssetsNavigatorModule ,
ReportsModule ,
NgUpgradeModule ,
DashboardUpgradeModule ,
CockpitDashboardModule ,
SensorPhoneModule ,
ReportDashboardModule ,
BinaryFileDownloadModule
],
// ---- 8< 追加箇所 ----
declarations : [HelloComponent ],
entryComponents : [HelloComponent ],
// ---- >8 ----
providers : [{
provide : HOOK_ONCE_ROUTE ,
useValue : [{
context : ViewContext .Device ,
path : 'hello' ,
component : HelloComponent ,
label : 'hello' ,
priority : 100 ,
icon : 'rocket'
}],
multi : true
}]
})
export class AppModule extends HybridAppModule {
constructor (protected upgrade : NgUpgradeModule ) {
super ();
}
}
npm start
でアプリケーションを開始する場合、デバイスの詳細ビューへアクセスすると以下のように表示されます。
これで、デバイスにタブが追加されました。
テナント、ユーザー、アプリケーションの詳細ビューでも同じことができます。
次は条件が満たされた場合のみ、このタブを表示する方法を学びましょう。
(付録)4.条件を満たした場合のみ、タブを表示する
場合によっては、特定の条件を満たした場合にのみ追加の情報を利用できることがあります。例えば、デバイスに位置情報のフラグメントが関連付けられている場合にのみ位置情報を表示することは意味があります。そのような条件を追加するには、コンテキストルートは Angularのガード概念 を継承します。
ガードを追加するには、単純にルート定義に canActivate
プロパティを追加するだけです。
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 , HOOK_ONCE_ROUTE , ViewContext } from '@c8y/ngx-components' ;
import { DashboardUpgradeModule , UpgradeModule , HybridAppModule , UPGRADE_ROUTES } from '@c8y/ngx-components/upgrade' ;
import { AssetsNavigatorModule } from '@c8y/ngx-components/assets-navigator' ;
import { CockpitDashboardModule , ReportDashboardModule } from '@c8y/ngx-components/context-dashboard' ;
import { ReportsModule } from '@c8y/ngx-components/reports' ;
import { SensorPhoneModule } from '@c8y/ngx-components/sensor-phone' ;
import { BinaryFileDownloadModule } from '@c8y/ngx-components/binary-file-download' ;
import { HelloComponent } from './hello.component' ;
// ---- 8< 追加箇所 ----
import { HelloGuard } from './hello.guard' ;
// ---- >8 ----
@ NgModule ({
imports : [
// Upgrade module must be the first
UpgradeModule ,
BrowserAnimationsModule ,
RouterModule .forRoot (),
NgRouterModule .forRoot ([...UPGRADE_ROUTES ], { enableTracing : false , useHash : true }),
CoreModule .forRoot (),
AssetsNavigatorModule ,
ReportsModule ,
NgUpgradeModule ,
DashboardUpgradeModule ,
CockpitDashboardModule ,
SensorPhoneModule ,
ReportDashboardModule ,
BinaryFileDownloadModule
],
declarations : [HelloComponent ],
entryComponents : [HelloComponent ],
providers : [
// ---- 8< 追加箇所 ----
HelloGuard ,
// ---- >8 ----
{
provide : HOOK_ONCE_ROUTE ,
useValue : [{
context : ViewContext .Device ,
path : 'hello' ,
component : HelloComponent ,
label : 'hello' ,
priority : 100 ,
icon : 'rocket' ,
// ---- 8< 追加箇所 ----
canActivate : [HelloGuard ]
// ---- >8 ----
}],
multi : true
}]
})
export class AppModule extends HybridAppModule {
constructor (protected upgrade : NgUpgradeModule ) {
super ();
}
}
これで、特定の条件をチェックするガードを作成できます。trueに解決されると、タブが表示され、それ以外の場合は表示されません。
デバイスの特定のフラグメントをチェックするガードはこの hello.guard.ts
のようになります。
import { Injectable } from '@angular/core' ;
import { CanActivate , ActivatedRouteSnapshot } from '@angular/router' ;
import { Observable } from 'rxjs' ;
@ Injectable ()
export class HelloGuard implements CanActivate {
canActivate (route : ActivatedRouteSnapshot ): Observable < boolean > | Promise< boolean > | boolean {
const contextData = route .data .contextData || route .parent .data .contextData ; // 1.
const { 'acme_HelloWorld' : helloWorldFragment } = contextData ; // 2.
return ! ! helloWorldFragment ;
}
}
上記の数字は以下のように説明できます。
これは、Angularルーターと整合していない唯一の部分です。コンテキストルートでは、CanActivate
が2回呼び出されます。1回は親ルートがアクティブになり、1回は子ルートがアクティブになります。最初の呼び出しは、タブを表示する必要があるかどうかを確認し、2番目の呼び出しは、ユーザーがそのタブに移動できるかどうかを確認します。したがって、 ActivatedRouteSnapshot
は両方の呼び出しで異なり、2番目のケースでは親から contextData
を解決する必要があります。
acme_HelloWorld
フラグメントがコンテキストに設定されているかを確認します。
APIに acme_HelloWorld
フラグメントを付与してデバイスをポストすると、他のデバイスではなく作成したデバイスのみにHello タブが表示されるでしょう。
結論
コンテキストルートは、既存のルートをさらに詳細に拡張するのに役立ちます。
同時に、コンテキストは一度のみ解決され、コンテキストが見つからない場合は親によって処理されるため、この概念によりアプリケーションの一貫性がが保たれます。
しかし、コンテキストルートの概念を抽象化し、独自に実装するデフォルトの方法は現時点では存在しません。
このコンセプトはAngularのルーティング に大きく基づいているので、自分で概念を実装することができます。
モジュールフェデレーションによるカスタムウィジェットプラグイン
バージョン: 1011.153.0 | パッケージ: @c8y/cli, @c8y/apps, @c8y/ngx-components
簡単なウィジェットの作成方法、その構造がどのように見えるか、アプリケーションへの追加方法については、ダッシュボードにカスタムウィジェットを追加する > ウィジェットコンポーネントを作成する をご覧ください。
次のチュートリアルでは、モジュールフェデレーションを使用してウィジェットをアプリケーションに追加する方法と、プロセスが前のチュートリアルとどのように異なるかを中心に説明します。
以下のソリューションは、Webpack 5で導入されたモジュールフェデレーション機能に完全に基づいています。
機能の詳細については、Webpack:モジュールフェデレーション をご覧ください。
1. ウィジェットプラグイン(例)を初期化する
以下に示すコマンドを使用して、サンプルプラグインを作成するための複数ステップのプロセスを開始します。
プラグイン名を選択します(例:widget-plugin)。
? Enter the name of the project : (my - application ) widget - plugin
サンプルアプリケーションを作成するバージョンを選択します(例:10.13.72.0(next))。
? Which base version do you want to scaffold from? (Use arrow keys)
1011.0.18 (latest)
❯ 1013.72.0 (next)
1013.0.63
1010.0.29
1009.0.33
1007.0.47
other
プラグインのベースとなるアプリケーションテンプレートを選択します(例:widget-plugin)。
? Which base project do you want to scaffold from?
administration
application
cockpit
devicemanagement
hybrid
tutorial
❯ widget-plugin
数秒後、次のようなメッセージが表示されます。
Application created. Go into the folder "widget-plugin" and run npm install
アプリケーションフォルダに移動し、npm install
を実行します。
アプリケーションフォルダは、以下の例のようになります。
このチュートリアルでは、最も重要なファイルは package.json と README.md
です。
app.module.spec.ts;
jest.config.js;
README.md;
tsconfig.spec.json;
app.module.ts;
package.json;
setup-jest.js;
widget/;
index.ts;
polyfills.ts;
tsconfig.json;
これで、モジュールフェデレーションを使用する最初のプラグインが作成されました。
2. カスタムウィジェットを作成時のアプローチの違い
シンプルなウィジェットとモジュールフェデレーションガイドラインに従ってビルドされたウィジェット間には、いくつかの違いがあります。
最大の違いは package.json ファイルで、isPackage
, package
, exports
などのフィールドが配置されています。
以下のリストは、フィールドとその役割を示しています。
isPackage
: アプリケーションがパッケージであるかどうかを示す。モジュールフェデレーションを利用して追加されたウィジェットの場合、値にtrue
を設定します。
package
: パッケージの種類(例:plugin
)
exports
: 重要なフィールドです。シェルアプリケーションのwidget-pluginで利用可能になるAngularモジュールを定義します(README.md ファイルもご覧ください)。:
name
: エクスポートされたモジュール名(Example widget plugin)。
module
: Angularモジュールクラス名(WidgetPluginModule)。
path
: モジュールを含む TypeScript ファイルへのパス。ファイルはネストされているため、次のパスを使用します。./widget/widget-plugin.module.ts
description
: モジュールの簡単な説明。
備考
プラグインを作成する場合、カスタムモジュールはこのアプローチの中核になります。エクスポートされたモジュールは、シェルと呼ばれるアプリケーションとプラグインをリンクするエントリポイントとして扱われます。既製の機能を含む必要があるいくつかのモジュールを作成して、エクスポートできます。
さらに、これらのモジュールは、遅延ロードモジュールのように動作します。一つの大きなパッケージとして事前に読み込まれるのではなく、オンデマンドで読み込まれる小さなパッケージのコレクションのようなものです。
各モジュールは、HOOKの概念によって追加機能を拡張することができます。詳しくは既存のアプリケーションを拡張しフックを使用する をご覧ください。例えば、プラグインはHOOK_NAVIGATOR_NODESを使用してナビゲーションメニューに別のエントリを追加することができます。詳細はナビゲーターノードを追加 をご覧ください。
また、ローカル開発サーバーの起動方法にも違いがあり、サーバーの役割については以下のステップをご覧ください。
3. ローカルサーバー、デバッグとデプロイメント
ローカルサーバー
新しいプラグインの作成プロセスを容易にするため、ローカルサーバーのコマンドに、すべてのリクエストをシェルアプリケーション「コックピット」に代理させる新しいフラグが追加されました。
npm install
を実行し、ローカルサーバーを起動します。
npm start -- -- shell cockpit
次のような出力が表示されます。
Shell application: cockpit
http://localhost:9000/apps/cockpit/index.html?remotes= %7B%22widget-plugin%22%3A%5B%22WidgetPluginModule%22%5D%7D
リンク先では、コックピットのログイン画面に転送されます。
ログイン後、ウィジェットの追加 画面で、widget-plugin
をダッシュボードに追加します。
ウィジェットの編集作業の残りの部分は、通常のウィジェットの編集作業と同じです。変更内容を確認するには、ブラウザを更新してください。
デバッグ
通常のウィジェットとモジュールフェデレーション用に変更されたウィジェットのpackage.json ファイルのもう一つの違いは、remote
フィールドです。(以下の例をご覧ください。)
. . .
"remotes" : {
"widget-plugin" : [ / / c o n t e x t P a t h
"WidgetPluginModule" / / m o d u l e c l a s s n a m e
]
}
. . .
備考
remotes
フィールドは、モジュールをインポートするために使用されます。モジュールを正しくインポートするには、プラグインのコンテキストパス (package.json の contextPath
フィールド) に続いて、モジュールのクラス名を指定します。
プラグインは、remotesというフィールドを介してそれ自体をインポートします。
エクスポートされたモジュールの正確性を検証する最初のステップとしてお勧めします。これにより、アプリケーションのデバッグが容易になります。
独自のモジュールをインポートした後、npm start
を実行して、ローカルサーバが起動するかどうかを確認します。
後でプラグインを確認するために、npm start -- --shell cockpit
を使用して、さまざまなシェルアプリケーションでローカルに制御することをお勧めします。
デプロイ
ウィジェットのアップロードは、通常のウィジェットと同じです。
以下のコマンドを順番に実行してください。
そして、
コンソールのプロンプトに従って、アプリケーションをテナントにデプロイします。
4. デプロイされたウィジェットをシェルアプリケーションに追加する
現在、モジュールフェデレーションに関連するビューとロジックは、ベータフラグの後ろに隠されています。
アップロードされたウィジェット プラグインをコックピットアプリケーションのダッシュボードに追加するには、以下のステップを実行します。
管理アプリケーションでベータフラグを有効にします。https://<yourTenantUrl>/apps/administration?beta=true
これで、管理アプリケーション > エコシステム > アプリケーション > パッケージ の パッケージ タブにアクセスできるようになり、プラグインの詳細を確認できます。
すでにカスタムコックピットアプリケーションがある場合は、その詳細 ページに移動し、プラグイン タブに移動します。Widget-pluginをインストールしてください。
コックピットアプリケーションの独自のバージョンがない場合は、管理アプリケーション >エコシステム >アプリケーション に移動し、アプリケーションを追加 をクリックします。表示されるダイアログで、既存のアプリケーションを複製
を選択します。アプリケーションのリストから、**コックピット(登録済み)**を選択します。名前 、アプリケーションキー 、パス など、利用可能なフィールドを編集します。デフォルトの値を使用し、次に進みます。複製したアプリケーションにwidget-plugin
をインストールします。
これで、カスタムウィジェットは、コックピットアプリケーションのバージョンで利用できるようになりました。
ウィジェットの追加のリストに、新しく追加されたウィジェットが利用可能なダッシュボードに移動します。
widget-plugin
は、管理アプリケーションの中からインストールされました。これが、ウィジェットに関する通常のアプローチと新しいアプローチとの主な違いです。
モジュール フェデレーションでは、アプリケーションが実行中 (ランタイム)に新しい機能を追加することができますが、古いアプローチでは、アプリケーションがビルド (コンパイル時)にしか新しい機能を追加することができませんでした。
既存のアプリケーションを拡張しフックを使用する
バージョン: 1009.0.18 | パッケージ: @c8y/cli, @c8y/apps, @c8y/ngx-components
コックピットやデバイス管理のような既存のアプリケーションを拡張することは、一般的なユースケースです。
カスタムルートでコックピットを拡張する方法、ナビゲータにこのルートを追加する方法を順番に説明していきます。
まず、何をハイブリッド アプリケーションと呼んでいるのか、@c8y/apps npmパッケージが何を含んでいるのか、という背景を紹介します。
背景
デフォルトのアプリケーションはThings Cloudに同梱されている3つのアプリケーションで構成されています。これらのアプリケーションは何年も開発が続けられた結果、コードは主にAngularJSベースとなっています。現在、すべての組み込みアプリケーションではAngularを使用しているため、双方のフレームワークを提供する方法が必要でした。 @c8y/cli
により、2つのフレームワークを共存させるデフォルトアプリケーションをスキャフォールドすることが可能になりました。これは、AngularとAngularJSのアプリケーションを同時に提供するためにAngularのアップグレード機能を用いています。これにより、新機能はすべてのAngular JS プラグインを統合しながら、Angular で新しい機能を開発できます。これを私たちは ハイブリッド モードと呼んでいます。
しかし、ハイブリッドモードにはいくつか制限があります。このレシピの後半で説明します。これらの制限により、純粋で 空のスターターAngularアプリケーションを提供することにしました。これはAngularJSのプラグインを統合する可能性がありません。このAngularのみのアプリケーションは以下の2つを含みます。
Angular CLI : Angular CLIを使用すると全Angularエコシステムの恩恵を受けることができるため、Angular CLIの多くのツールを再利用できます(テストなど)。
@c8y/cli :私たちのツールとうまく統合する事前に用意されている方法ですが、特別なケースは許可しない可能性が高いです。
Web SDKを使い始めるには、全部で3つの可能性があります。
既存のハイブリッドアプリケーションを拡張する。
Angular CLIで純粋なAngularアプリケーションをビルドする。
@c8y/cliでビルドする。
どれを選択するかは、ビルドするアプリケーションに大きく依存します。
例えば、プラットフォームのルックアンドフィールに従ったアプリケーションが必要であるが、マテリアル フレームワークなど特定のシナリオに特別な依存関係を使用したい場合は、純粋な Angular CLI ソリューションを使用するのが最適です。
最も一般的なユースケースは、ハイブリッドアプリケーションの拡張で、このレシピではこれを取り上げます。
まず、このアプローチの限界を見て、なぜこの概念がそのように設計されているのかを理解しましょう。
ハイブリッドモードの制限
ハイブリッドアプリケーションを実行する時、AngularとAngularJSは並行して実行されるようにする必要があるため、いくつかの制限があります。
index.html
にアクセスできない: ブートストラップの処理全体はThings Cloudが処理し、AngularとAngularJSの必要な要素がすべて揃っていることを確認する必要があります。ブートストラップ テンプレートを変更することはできず、ルートを追加することのみ可能です。
サービスは最初に読み込まれる必要があるため、ルートアプリモジュールにサービスを注入することもできません。サービスの宣言部分で、ルートもしくは providedIn: root
として提供する必要があります。
ルーターのルートは UPGRADED_ROUTES
の前で定義されていなければなりません。この理由は、Angularのルーターは UPGRADED_ROUTES
で定義されているすべてのAngularJSのルートに合致する **
というパスを持っているためです。UPGRADED_ROUTES
の前で定義する場合、定義したルートの前に **
パスが合致します。
すべての拡張はフックを通して行う必要があります。これはハイブリッドアプリケーションではAngularとAngularJSが必要であり、双方でフックが使われる可能性があるためです。
スタイリングはグローバルのスタイル変更に制限されています。つまり、カスタムブランディングを適用するか、インラインスタイルを使用することのみよって拡張可能になります。このバージョンでは styleUrls
はサポートされていません。
制限について理解したところで最初のアプリケーションを拡張し、拡張用フックを開発することができます。これを行うには、ハイブリッドアプリケーションをスキャフォールディングにしておく必要があります。
c8y/apps
は、デフォルトのアプリケーションとその最小限のセットアップを含むパッケージです。
c8ycli
は new
コマンドでアプリケーションを初期化するたびにこのパッケージを使用します。
次のセクションでは、スキャフォールディングプロセスとハイブリッドアプリケーションを拡張する方法について順を追って説明します。
1. サンプルアプリケーションを初期化する
まずはじめに、アプリケーションが必要です。c8ycli
を利用して新しくコックピットアプリケーションを作成します。
c8ycli new my - cockpit cockpit - a @ c8y / apps @ 1009.0 .18
次に、依存関係をインストールします。新しく作成されたフォルダに移動し npm install
を実行してください。
備考
c8y cli new
コマンドには、スキャフォールディングに使用するパッケージを定義する
-a
フラグがあります。このようにして、スキャフォールディングとするアプリケーションのバージョンを定義することも可能です。例えば、以下の通りです。
c8ycli new my-cockpit cockpit -a @c8y/apps@1004.11.0
は、アプリケーションのバージョン10.9.0.18
をスキャフォールディングにします。
c8ycli new my-cockpit cockpit -a @c8y/apps@latest
は、アプリケーションの最新公式リリースをスキャフォールディングにします。-a
フラグを付けずに使用する場合と同じです。
c8ycli new my-cockpit cockpit -a @c8y/apps@next
は、アプリケーションの最新ベータリリースをスキャフォールディングにします。
2. ルートにカスタムコンポーネントをバインドする
ルートはAngularと同じ方法で追加することができます。
唯一の違いは、ハイブリッドの制限のため、UPGRADE_ROUTES
の前に定義する必要があることです。
プロジェクト内に以下の内容で hello.component.ts
ファイルを作成します。
import {Component } from "@angular/core" ;
@ Component ({
selector : "app-hello" ,
template : `
<c8y-title>Hello</c8y-title>
World
` ,
})
export class HelloComponent {
constructor () {}
}
これは、基本的なコンポーネントです。
テンプレートだけは、タイトルを表示するために「コンテンツ投影」という特別な機能を使用しています。
コンテンツ投影とは、コンテンツを定義されている場所以外の場所に表示するために使用されるAngularの概念です。
どのコンポーネントがコンテンツ投影をサポートしているかについては、コンポーネントライブラリ(ngx) ドキュメントをご覧ください。
以下の方法で app.module.ts
を変更することによりルートにカスタムコンポーネントをバインドできるようになります。
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 {DashboardUpgradeModule , UpgradeModule , HybridAppModule , UPGRADE_ROUTES } from "@c8y/ngx-components/upgrade" ;
import {AssetsNavigatorModule } from "@c8y/ngx-components/assets-navigator" ;
import {CockpitDashboardModule , ReportDashboardModule } from "@c8y/ngx-components/context-dashboard" ;
import {ReportsModule } from "@c8y/ngx-components/reports" ;
import {SensorPhoneModule } from "@c8y/ngx-components/sensor-phone" ;
import {BinaryFileDownloadModule } from "@c8y/ngx-components/binary-file-download" ;
// --- 8< 変更箇所----
import { HelloComponent } from './hello.component' ; // 1
// --- >8 ----
@ NgModule ({
// --- 8< 変更箇所----
declarations : [HelloComponent ], // 2
// --- >8 ----
imports : [
// Upgrade module must be the first
UpgradeModule ,
BrowserAnimationsModule ,
RouterModule .forRoot (),
NgRouterModule .forRoot (
// --- 8< 変更箇所----
{ path : 'hello' , component : HelloComponent }, // 3
// --- >8 ----
...UPGRADE_ROUTES
], { enableTracing : false , useHash : true }),
CoreModule .forRoot (),
AssetsNavigatorModule ,
ReportsModule ,
NgUpgradeModule ,
DashboardUpgradeModule ,
CockpitDashboardModule ,
SensorPhoneModule ,
ReportDashboardModule ,
BinaryFileDownloadModule ,
]
})
export class AppModule extends HybridAppModule {
constructor (protected upgrade : NgUpgradeModule ) {
super ();
}
}
ここでの変更は単純です。まずはじめに、(1)コンポーネントをインポートします。(2)次に、そのコンポーネントを宣言部へ追加します。(3)最後にこの場合では hello
というパスへバインドする必要があります。「c8ycli server
」コマンドでアプリケーションを起動し、正しいハッシュを追加して URLhttp://localhost:9000/apps/cockpit/#/hello
)に移動すると(、カスタム コンポーネントが表示されます。次のステップでは、左ナビゲータのコンポーネントを追加します。
3. ナビゲーターノードをフックする
新しく作成した hello.component.ts
にユーザーが移動できるようにするために、左側のナビゲーターにナビゲーションを追加します。
そのためには、いわゆるフックを使用します。
フックは特定のインジェクション トークンにバインドされている単なるプロバイダーです。
複数のプロバイダーを追加できるようにするには、Angularのマルチプロバイダーの概念を使います。
詳しく説明することは、このチュートリアルの範囲を超えています。
angular.ioドキュメント をご覧ください。
インジェクショントークンは @c8y/ngx-components
パッケージをインポートすることで受け取ることができます。
これらはすべて HOOK_
で始まり、その後に何に使われるかが続きます。
例えば、ナビゲータノードを追加するには HOOK_NAVIGATOR_NODE
を使用します。
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" ;
// --- 8< 変更箇所----
import {CoreModule , RouterModule , HOOK_NAVIGATOR_NODES , NavigatorNode } from "@c8y/ngx-components" ;
// --- >8 ----
import {DashboardUpgradeModule , UpgradeModule , HybridAppModule , UPGRADE_ROUTES } from "@c8y/ngx-components/upgrade" ;
import {AssetsNavigatorModule } from "@c8y/ngx-components/assets-navigator" ;
import {CockpitDashboardModule , ReportDashboardModule } from "@c8y/ngx-components/context-dashboard" ;
import {ReportsModule } from "@c8y/ngx-components/reports" ;
import {SensorPhoneModule } from "@c8y/ngx-components/sensor-phone" ;
import {BinaryFileDownloadModule } from "@c8y/ngx-components/binary-file-download" ;
@ NgModule ({
declarations : [HelloComponent ],
imports : [
// Upgrade module must be the first
UpgradeModule ,
BrowserAnimationsModule ,
RouterModule .forRoot (),
NgRouterModule .forRoot (
[
{path : "hello" , component : HelloComponent }, // 3
...UPGRADE_ROUTES ,
],
{
enableTracing : false ,
useHash : true ,
}
),
CoreModule .forRoot (),
AssetsNavigatorModule ,
ReportsModule ,
NgUpgradeModule ,
DashboardUpgradeModule ,
CockpitDashboardModule ,
SensorPhoneModule ,
ReportDashboardModule ,
BinaryFileDownloadModule ,
],
// --- 8< 変更箇所----
providers : [
{
provide : HOOK_NAVIGATOR_NODES , // 1
useValue : [{ // 2
label : 'Hello' , // 3
path : 'hello' ,
icon : 'rocket' ,
priority : 1000
}] as NavigatorNode [], // 4
multi : true // 5
}
]
// --- >8 ----
})
export class AppModule extends HybridAppModule {
constructor (protected upgrade : NgUpgradeModule ) {
super ();
}
}
上記の数字の説明は以下の通りです。
HOOK_NAVIGATOR_NODES
を提供します。
特定のvalueを使います。複雑なケースでは useClass
や get()
を定義することもできます。
ナビゲータ ノードがどのように見えるかを定義します。
フックのほとんどはTypeScriptで先行入力できるインターフェースを持っています。
マルチプロバイダーフラグは複数のフックが存在する可能性があることをAngularに伝えます。
この拡張フックを実装すると、ナビゲータに次のような新しいエントリーが表示されます。
NavigatorNode
インターフェースのプロパティ priority
は、ノードの表示順を定義することに注意してください。。
これで hello.component.ts
は、コックピットアプリケーション内の空白のキャンバスのようになりました。
コックピットの所定の機能に影響を与えることなく、必要な機能をすべて実装することができます。
まとめ
ハイブリッドアプリケーションは、Angular JSとAngularの統合による制限があります。
しかし、フックの概念とカスタムルートによって、既存のハイブリッドアプリケーションに追加できるようになります。
これらは、組み込みアプリケーションを拡張するための強力なツールです。
追加機能が必要な場合は、純粋なAngularアプリケーションの方が適していることもあります。
これはユースケースによって異なります。
ログインページと認証の削除
バージョン: 1009.0.18 | パッケージ: @c8y/cli, @c8y/apps, @c8y/ngx-components
備考
この手法では、ユーザー名とパスワードが公開されます。このユーザーが重要なデータにアクセスできないことを確認してください。
デフォルトのアプリケーションでは、ページにアクセスする前に必ずログインページに移動して認証を行います。
このレシピでは、ログイン認証を解除してアプリケーションを直接使用する方法を説明します。
背景
すべての認証を削除することはできません。
これを回避するためには、アプリケーションがリクエスト時に読み取るデフォルトの認証情報を渡す必要があります。目標は、アプリケーションが認証されていないためにログインページをリクエストする前に、デフォルトの認証情報を使ってログインをトリガーすることです。
ログイン機能は @c8y/ngx-components
パッケージの CoreModule
に含まれており、Angular がアプリケーションをブートストラップする際に読み込まれます。その前に、デフォルトの認証情報をAPIに渡す必要があります。その結果、Angularが最初のページを読み込むとき、ユーザーはすでに認証されており、ログインページはスキップされることになります。
1. 新しいアプリケーションを初期化する
出発点として、アプリケーションが必要です。そのために、c8cycli
を使用して新しいアプリケーションを作成します。
c8ycli new my - cockpit cockpit - a @ c8y / apps @ 1009.0 .18
これにより、コックピットアプリケーションの完全なコピーである新しいアプリケーションが作成されます。
次に、すべての依存関係をインストールする必要があります。
新しいフォルダに移動して、npm install
を実行します。
備考
c8ycli new
コマンドには
-a
フラグがあり、スキャフォールディングするパッケージを定義することができます。このようにして、スキャフォールディングするアプリケーションのバージョンを定義することも可能です。例えば、以下の通りです。
c8cycli new my-cockpit cockpit -a @c8y/apps@1009.0.18
は、アプリケーションのバージョン1009.0.18
をスキャフォールディングにします。
c8ycli new my-cockpit cockpit -a @c8y/apps@latest
は、アプリケーションの最新公式リリースをスキャフォールディングにします。-a
フラグを付けずに使用する場合と同じです。
c8ycli new my-cockpit cockpit -a @c8y/apps@next
は、アプリケーションの最新ベータリリースをスキャフォールディングにします。
2. デフォルト認証のロジックを追加する
まず、Angularがカスタムアプリケーションをブートストラップする前に、デフォルトの認証を追加する必要があります。
そのため、新しく作成したカスタムコックピットアプリケーションの app.module.ts
に、ログイン前にトリガーされる新しいプロバイダーを追加する必要があります。
そのためには、Angularのインジェクション トークンAPP_INITIALIZER
を使用します。
このトークンによって、新機能が実行されるまでアプリケーションが初期化されないようになります。
providers : [
{
provide : APP_INITIALIZER ,
useFactory : initApp ,
multi : true ,
deps : [LoginService ],
},
];
ファクトリー関数 initApp
を使用し、ここでデフォルトの認証を定義して送信します。
APIに認証情報を送信するには、@c8y/ngx-components
の一部である LoginService
(http://resources.cumulocity.com/documentation/websdk/ngx-components/injectables/LoginService.html ) に依存関係を追加してください。
export function initApp (loginService : LoginService ) {
return () => {
const credentials = {
tenant : "tenantName" ,
user : "admin" ,
password : "C8Yadmin" ,
};
const basicAuth = loginService .useBasicAuth (credentials );
return loginService .login (basicAuth , credentials );
};
}
デフォルトの認証情報でログインするには、サービスからログイン機能 を呼び出して、認証方法とデフォルトの認証情報を渡す必要があります。
これでレシピは完成し、認証はバックグラウンドで行われるようになります。
// --- 8< 変更箇所 ----
import { APP_INITIALIZER , NgModule } from "@angular/core" ;
// --- >8 ----
import { BrowserAnimationsModule } from "@angular/platform-browser/animations" ;
import { RouterModule as NgRouterModule } from "@angular/router" ;
import { UpgradeModule as NgUpgradeModule } from "@angular/upgrade/static" ;
// --- 8< 変更箇所 ----
import { CoreModule , LoginService , RouterModule } from "@c8y/ngx-components" ;
// --- >8 ----
import { DashboardUpgradeModule , UpgradeModule , HybridAppModule , UPGRADE_ROUTES } from '@c8y/ngx-components/upgrade' ;
import { AssetsNavigatorModule } from '@c8y/ngx-components/assets-navigator' ;
import { CockpitDashboardModule , ReportDashboardModule } from '@c8y/ngx-components/context-dashboard' ;
import { ReportsModule } from '@c8y/ngx-components/reports' ;
import { SensorPhoneModule } from '@c8y/ngx-components/sensor-phone' ;
import { BinaryFileDownloadModule } from '@c8y/ngx-components/binary-file-download' ;
@ NgModule ({
imports : [
// Upgrade module must be the first
UpgradeModule ,
BrowserAnimationsModule ,
RouterModule .forRoot (),
NgRouterModule .forRoot ([...UPGRADE_ROUTES ], { enableTracing : false , useHash : true }),
CoreModule .forRoot (),
AssetsNavigatorModule ,
ReportsModule ,
NgUpgradeModule ,
DashboardUpgradeModule ,
CockpitDashboardModule ,
SensorPhoneModule ,
ReportDashboardModule ,
BinaryFileDownloadModule
],
providers : [
{
provide : APP_INITIALIZER ,
useFactory : initApp ,
multi : true ,
deps : [LoginService ],
},
],
})
export class AppModule extends HybridAppModule {
constructor (protected upgrade : NgUpgradeModule ) {
super ();
}
}
export function initApp (loginService : LoginService ) {
return () => {
const credentials = {
tenant : "tenantName" ,
user : "admin" ,
password : "C8Yadmin" ,
};
const basicAuth = loginService .useBasicAuth (credentials );
return loginService .login (basicAuth , credentials );
};
}
結論
このチュートリアルでは、カスタムアプリケーションを開発する際に、認証を解除する方法を説明しています。
この種の手法は、アプリケーションが機密情報を持っていない場合に使用することができます。
データ保護が必要な場合は、この手法を避ける必要があります。
カスタムマイクロサービスからデータをリクエストする
バージョン: 1009.0.18 | パッケージ: @c8y/cli, @c8y/apps, @c8y/ngx-components
場合によって、UIはカスタムマイクロサービスからのデータを必要とすることがあります。そのデータは、Angular のHttpModule
などの任意の HTTP クライアントでいつでも読み取ることができますが、すぐに使用できる認証が必要な場合があります。
このレシピでは @c8y/client
を使用してカスタムエンドポイントにアクセスする方法と自動的に認証する方法を紹介します。まず、Angularアプリケーションにおいてクライアントがどのように動作するのか説明するために基本を見ていきましょう。
基本: クライアントがどのように動作するか
どのように @c8y/client
が動作するのか、利点は何かを見ていきましょう。
クライアントはブラウザ(もしくはnode.js)からプラットフォームへのHTTPリクエストを処理します。ほとんどのプラットフォームAPIはセキュアなため、認証情報を設定できます。
現在、認証方法には2つのオプションがあります。
BasicAuth
: 認証の詳細を含む各リクエストにヘッダーを追加します。JavaScriptでパスワードを読み取ることができるため、安全性は低くなります。
CookieAuth
: バックエンドが設定したCookieを読み込んで、プラットフォームへのアクセスを許可します。CookieはHTTPリクエストごとに送信されるため、クロスサイトスクリプティング攻撃を防ぐためにXSRF-TOKEN
ヘッダーを設定する以外は、この方法による特別な認証処理はありません。
新しいクライアントインスタンスで認証方法を設定する際に、どの認証を使用するかを定義することができます。
クライアントは、プラットフォームのすべての共通エンドポイントを持つオブジェクトを返します。
例えば、次の例では BasicAuth
を介してインベントリからデータをリクエストします。
const client = new Client (new BasicAuth ({
user : 'admin' ,
password : 'password' ,
tenant : 'acme'
}), 'https://acme.je1.thingscloud.ntt.com' );
try {
const { data , paging , res } = await client .inventory .list ();
console .log ('Login with admin:password successful' );
console .log (data );
} catch (ex ) {
console .log ('Login failed: ' , ex )
}
事前設定された各エンドポイントは、data
含むオブジェクト、オプションの paging
オブジェクトおよび res
オブジェクトを返します。レスポンスは、最新のすべてのブラウザに実装されている次世代の XHR API である フェッチ によって提供されます(IE11 ではポリフィル可能)。
つまり、@c8y/client
はJavaScript のヘルパー ライブラリであり、フェッチを抽象化して、簡単な認証と共通プラットフォーム API への直接アクセスできるようにします。
次のセクションでは、Angular の依存性注入(DI)モデルを利用して、Angular アプリケーションでその概念を使用する方法を説明します。
基本: @c8y/clientとAngularアプリケーションの相互作用
@c8y/ngx-components
はアプリケーションの起動を可能にするAngularのコンポーネントです。例えばコックピット、管理、デバイス管理などの基本アプリケーションでログイン画面を表示するのによく使用されます。新しくAngularベースのアプリケーションを起動する時は常に @c8y/client
と @c8y/ngx-components
が含まれています。さらに、ngx-componentsには @c8y/ngx-components/api
と呼ばれ、DataModule
をエクスポートするサブパッケージがあります。このモジュールはすでに、すべての一般的なエンドポイント サービスをインポートしているため、Angular の標準的な依存性注入を使用してデータにアクセスできます。
上記のAngularアプリケーションの例は、以下のようになります。
import { InventoryService } from '@c8y/client' ; // 1
@ Component ({
selector : '[app-hello]' ,
template : ` <h1>hello</h1> `
})
export class HelloComponent {
constructor (public inventory : InventoryService ) {} // 2
async ngOnInit () {
const { data , paging , res } = await client .inventory .list (); // 3
console .log (data );
}
}
クライアントから目的サービスをインポートする。
目的のサービスを使用するために依存性注入します。AngularのDIの概念は DataModule
がメインモジュールに正しくインポートされた場合に。必要なすべての依存関係を処理します。
ここでデータをリクエストできるようになります。認証はすでに処理されています。コンストラクタで直接使用するか、またはEntryComponent
として使用すると、コンポーネントがログイン モジュールの前に読み込まれるため、リクエストは不正に失敗する可能性があります。これを回避するには、AppStateService
を注入できます。これにより、ユーザーがログインするとすぐに更新する currentUser
ovservableを提供します。
ここでは一般的なエンドポイントの使い方の概要を説明しました。
次のレシピではカスタムエンドポイントの追加方法を紹介します。
1. サンプルアプリケーションを初期化する
はじめに、ダッシュボードを表示するアプリケーションが必要です。
このため、c8ycli
を使用して新しいコックピットアプリケーションを作成します。
c8ycli new my - cockpit cockpit - a @ c8y / apps @ 1009.0 .18
次に、すべての依存関係をインストールする必要があります。
新しいフォルダに切り替えて、npm install
を実行します。
備考
c8ycli new
コマンドには、スキャフォールディングに使用するパッケージを定義する
-a
フラグがあります。このようにして、スキャフォールディングとするアプリケーションのバージョンを定義することも可能です。例えば、以下の通りです。
c8cycli new my-cockpit cockpit -a @c8y/apps@1009.0.18
は、アプリケーションのバージョン 1009.0.18
をスキャフォールディングにします。
c8ycli new my-cockpit cockpit -a @c8y/apps@latest
は、アプリケーションの最新公式リリースをスキャフォールディングにします。-a
フラグを付けずに使用する場合と同じです。
c8ycli new my-cockpit cockpit -a @c8y/apps@next
は、アプリケーションの最新ベータリリースをスキャフォールディングにします。
2. フェッチで直接データをリクエストする
HTTP GET でエンドポイント service/acme
からデータにアクセスしたい場合、認証付きでこれを実現する最も簡単な方法はクライアントの fetch
の実装を再利用することです。
アプリケーションにファイルを追加し、acme.component.ts
という名前を付けます。
import { Component , OnInit } from '@angular/core' ;
import { FetchClient } from '@c8y/client' ;
@ Component ({
selector : 'app-acme' ,
template : '<h1>Hello world</h1>{{data | json}}'
})
export class AcmeComponent implements OnInit {
data : any ;
constructor (private fetchClient : FetchClient ) {} // 1
async ngOnInit () {
const response = await this .fetchClient .fetch ('service/acme' ); // 2
this .data = await response .json (); // 3
}
}
クライアントで使用されている fetch
を抽象化した FetchClient
を注入します。
fetchClient.fetch
でデータをリクエストします。この関数は Fetch API と同じです。ただし、メソッドやデータを受け入れる 2 番目のパラメータとして、認証をプラットフォームに追加する点が異なります。
データをパースし、コントローラに設定してテンプレートに表示します。
次に、コンポーネントを表示できるアプリケーションへのルートを追加します。次のコードは、app.module.ts
でルートの追加を行います。詳細については、他のチュートリアルもご覧ください。
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 { DashboardUpgradeModule , UpgradeModule , HybridAppModule , UPGRADE_ROUTES } from '@c8y/ngx-components/upgrade' ;
import { AssetsNavigatorModule } from '@c8y/ngx-components/assets-navigator' ;
import { CockpitDashboardModule , ReportDashboardModule } from '@c8y/ngx-components/context-dashboard' ;
import { ReportsModule } from '@c8y/ngx-components/reports' ;
import { SensorPhoneModule } from '@c8y/ngx-components/sensor-phone' ;
import { BinaryFileDownloadModule } from '@c8y/ngx-components/binary-file-download' ;
// ---- 8< 追加箇所 ----
import { AcmeComponent } from './acme.component' ;
// ---- >8 ----
@ NgModule ({
imports : [
// Upgrade module must be the first
UpgradeModule ,
BrowserAnimationsModule ,
RouterModule .forRoot (),
// ---- 8< 追加箇所 ----
NgRouterModule .forRoot ([
{ path : 'acme' , component : AcmeComponent },
...UPGRADE_ROUTES ,
], { enableTracing : false , useHash : true }),
// ---- >8 ----
CoreModule .forRoot (),
AssetsNavigatorModule ,
ReportsModule ,
NgUpgradeModule ,
DashboardUpgradeModule ,
CockpitDashboardModule ,
SensorPhoneModule ,
ReportDashboardModule ,
BinaryFileDownloadModule
],
// ---- 8< 追加箇所 ----
declarations : [
AcmeComponent
]
// ---- >8 ----
})
export class AppModule extends HybridAppModule {
constructor (protected upgrade : NgUpgradeModule ) {
super ();
}
}
3. アプリケーションを実行し検証する
c8ycli server
でアプリケーションを実行し、ブラウザでモジュールで定義されたパスhttp://localhost:9000/apps/cockpit/#/acme
を参照すると、次のように表示されます。
このコンテキストパスで実行しているマイクロサービスが存在しないため、リクエストは失敗します。しかし、開発者ツールでわかるように、リクエストには authorization の Cookie が付属されています。つまり、マイクロサービスが存在すればリクエストは成功し、データは表示されます。
4.付録:Service.tsの抽象化を記述する
上記の例では、カスタムマイクロサービスへ直接アクセスして、に基本的な fetch
抽象化を使用しました。クライアントの共通サービスについても、同様の単純さを実現したい場合があります。これは内部で URL と JSON 解析を処理します。そのためには、@c8y/client
によって返された Service
クラスを拡張し、必要なメソッドまたはプロパティを上書きします。
acme.service.ts
という新しいファイルを作成し、acme
マイクロサービスの例で実践してみましょう。
import { Injectable } from '@angular/core' ;
import { Service , FetchClient } from '@c8y/client' ;
@ Injectable ({
providedIn : 'root'
})
export class AcmeService extends Service < any > { // 1
baseUrl = 'service' ; // 2
listUrl = 'acme' ;
constructor (client : FetchClient ) { // 3
super (client );
}
detail (entityOrId ) { // 4
return super .detail (entityOrId );
}
list (filter ? ) { // 4
return super .list (filter );
}
}
上記の数字の説明は、以下の通りです。
サービスを拡張することにより、@c8y/client
のあるすべての共通サービスと同じ機能が得られます。この場合、ジェネリック型は例を簡単にするために any
に設定しています。このサービスで送信するデータを反映したインターフェースを作成し、any
をこのインターフェースに置き換えるのが一般的なパターンです。
URLはこのサービスの主なエントリーポイントです。このパターンは常に <<url>>/<<baseUrl>>/<<listUrl>>/<id>
になります。マイクロサービスが異なる構造を採用する場合、Serviceクラスの getUrl
メソッドを上書きできます。
コンストラクタは依存性注入によってインポートされた FetchClient
が必要です。また、super()
を介して拡張した Service
クラスに渡す必要があります。リアルタイムをサポートするためにエンドポイントが必要な場合、ここに RealTime
抽象化を注入し、渡さなければいけません。
detail()
または list()
の実装を上書きできます。superメソッドのみを呼び出したり、superコールの結果を変更、または独自の実装を記述したりできます。どちらを選択するかは、マイクロサービスの実装の詳細によって異なります。
これで acme.component.ts
の AcmeService
を再利用できます。
import { Component , OnInit } from '@angular/core' ;
import { AcmeService } from './acme.service' ;
import { AlertService } from '@c8y/ngx-components' ;
@ Component ({
selector : 'app-acme' ,
template : '<h1>Hello world</h1>{{data | json}}'
})
export class AcmeComponent implements OnInit {
data : any ;
constructor (private acmeService : AcmeService , private alert : AlertService ) {} // 1
async ngOnInit () {
try {
const { data } = await this .acmeService .list (); // 2
this .data = data ;
} catch (ex ) {
this .alert .addServerFailure (ex ); // 3
}
}
}
(1)単純にサービスを注入し、(2)サービスで直接 list
のリクエストを実行します。(3)サービスはエラーを投げるため、try/catchで呼び出しをラッピングし、エラー時には addServerFailure
メソッドに例外を追加することで alert
を表示します。
まとめ
上記の例では、クライアント経由でカスタムマイクロサービスにアクセスする方法を示しています。AngularのHttpModule
のようなよく知られたクライアント抽象化を使う方が簡単かもしれませんが、@c8y/client
を再利用することで、すぐに認証を得ることができます。
このソリューションは、基になる変更を気にせずに @c8y/client
を更新できるので、変更に対して堅牢なソリューションです。