From af2c58689bb0f9a20b92983594a647a9606b38b6 Mon Sep 17 00:00:00 2001 From: "c8y-ui-release[bot]" Date: Wed, 20 Nov 2024 09:16:34 +0000 Subject: [PATCH] chore: Upgrade to Web SDK v1021.5.0 --- cumulocity.config.ts | 9 ++- package-lock.json | 80 +++++++++---------- package.json | 14 ++-- .../service/client/basic-view.component.html | 44 ++++++++++ .../service/client/basic-view.component.ts | 13 +++ .../service/client/counter.component.html | 15 ++++ src/hooks/service/client/counter.component.ts | 21 +++++ src/hooks/service/client/index.ts | 3 + .../service/client/service-hook.module.ts | 29 +++++++ src/hooks/service/counter/counter.model.ts | 43 ++++++++++ src/hooks/service/counter/counter.module.ts | 13 +++ src/hooks/service/counter/counter.service.ts | 10 +++ .../service-hook-codex-sample.module.ts | 13 +++ 13 files changed, 259 insertions(+), 48 deletions(-) create mode 100644 src/hooks/service/client/basic-view.component.html create mode 100644 src/hooks/service/client/basic-view.component.ts create mode 100644 src/hooks/service/client/counter.component.html create mode 100644 src/hooks/service/client/counter.component.ts create mode 100644 src/hooks/service/client/index.ts create mode 100644 src/hooks/service/client/service-hook.module.ts create mode 100644 src/hooks/service/counter/counter.model.ts create mode 100644 src/hooks/service/counter/counter.module.ts create mode 100644 src/hooks/service/counter/counter.service.ts create mode 100644 src/hooks/service/service-hook-codex-sample.module.ts diff --git a/cumulocity.config.ts b/cumulocity.config.ts index 9dd60d3..c671577 100644 --- a/cumulocity.config.ts +++ b/cumulocity.config.ts @@ -1,6 +1,6 @@ import { ConfigurationOptions } from '@c8y/devkit'; import { DefinePlugin } from 'webpack'; -import { author, description, version, name } from './package.json'; +import { author, description, name, version } from './package.json'; export default { runTime: { @@ -261,6 +261,13 @@ export default { description: 'A sample for wizard hook.', scope: 'self' }, + { + name: 'Service hook Codex sample', + module: 'ServiceHookCodexSampleModule', + path: './src/hooks/service/service-hook-codex-sample.module.ts', + description: 'A sample for hookService.', + scope: 'self' + }, { name: 'Stepper', module: 'StepperHookModule', diff --git a/package-lock.json b/package-lock.json index 6e51899..ce5d959 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "tutorial", - "version": "1021.4.3", + "version": "1021.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tutorial", - "version": "1021.4.3", + "version": "1021.5.0", "dependencies": { "@angular/animations": "^18.2.0", "@angular/cdk": "^18.2.10", @@ -17,10 +17,10 @@ "@angular/platform-browser": "^18.2.0", "@angular/platform-browser-dynamic": "^18.2.0", "@angular/router": "^18.2.0", - "@c8y/bootstrap": "1021.4.3", - "@c8y/client": "1021.4.3", - "@c8y/ngx-components": "1021.4.3", - "@c8y/style": "1021.4.3", + "@c8y/bootstrap": "1021.5.0", + "@c8y/client": "1021.5.0", + "@c8y/ngx-components": "1021.5.0", + "@c8y/style": "1021.5.0", "leaflet": "1.9.4", "ngx-bootstrap": "18.0.0", "rxjs": "^7.8.1", @@ -31,8 +31,8 @@ "@angular-devkit/build-angular": "^18.2.10", "@angular/cli": "^18.2.10", "@angular/compiler-cli": "^18.2.0", - "@c8y/devkit": "1021.4.3", - "@c8y/options": "1021.4.3", + "@c8y/devkit": "1021.5.0", + "@c8y/options": "1021.5.0", "@types/jasmine": "~5.1.0", "jasmine-core": "~5.2.0", "karma": "~6.4.0", @@ -361,9 +361,9 @@ } }, "node_modules/@angular/cdk": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.13.tgz", - "integrity": "sha512-yBKoqcOwmwXnc5phFMEEMO130/Bz9beQLJrKzIS87f6TXaGCeBs4xrPHq2i7Xx/2TqvMiOD9ucjmlVbtGvNG3w==", + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.14.tgz", + "integrity": "sha512-vDyOh1lwjfVk9OqoroZAP8pf3xxKUvyl+TVR8nJxL4c5fOfUFkD7l94HaanqKSRwJcI2xiztuu92IVoHn8T33Q==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -2692,9 +2692,9 @@ } }, "node_modules/@c8y/bootstrap": { - "version": "1021.4.3", - "resolved": "https://registry.npmjs.org/@c8y/bootstrap/-/bootstrap-1021.4.3.tgz", - "integrity": "sha512-5nNEx4xcIcn8PC1DkARiGoiq5ze87sHH3qQ1fNYnx1bwXOggzTuN8Ap0P8ik2qR6K2xbmY4umTw1xKU/xyio1Q==", + "version": "1021.5.0", + "resolved": "https://registry.npmjs.org/@c8y/bootstrap/-/bootstrap-1021.5.0.tgz", + "integrity": "sha512-EWSWy1hZU5iP6p208SCDAduhsn/4e+mx4HVgvXcRxYtdSJOBpoVz0bypYuZjS1mXt7NE5EUO/0YVQMK+7Rjqtw==", "license": "Apache-2.0", "dependencies": { "chroma-js": "2.4.2", @@ -2705,9 +2705,9 @@ } }, "node_modules/@c8y/client": { - "version": "1021.4.3", - "resolved": "https://registry.npmjs.org/@c8y/client/-/client-1021.4.3.tgz", - "integrity": "sha512-Sqja0oN7BQXsGF+NQ4q9+xN/5pA5f8zDdFbWxr102Ft3nxxYWa1+76NVV6CmMywENm9YA4Hy23+fW/G/CrGjvg==", + "version": "1021.5.0", + "resolved": "https://registry.npmjs.org/@c8y/client/-/client-1021.5.0.tgz", + "integrity": "sha512-yUeAkYdxtSfWNkPRne2MzdCEwBYiZrnw6403gkHtGI/KovNdzN9F3OE2EP9UAUnUZleCGQlAlVCwelaxhSe0OA==", "license": "Apache-2.0", "dependencies": { "@types/cometd": "4.0.8", @@ -2736,9 +2736,9 @@ "license": "MIT" }, "node_modules/@c8y/devkit": { - "version": "1021.4.3", - "resolved": "https://registry.npmjs.org/@c8y/devkit/-/devkit-1021.4.3.tgz", - "integrity": "sha512-ZDVEvEokMsRokjeYBSbRWBklrswLc7plm76d1H5mrzL/jMIl6PcgWyhR3TsUmDWPSO2vepNp/mr9agzGTtDE/A==", + "version": "1021.5.0", + "resolved": "https://registry.npmjs.org/@c8y/devkit/-/devkit-1021.5.0.tgz", + "integrity": "sha512-kYCnc27mwV7YrukqbsaPYCacWJbqA3ZZJEBbEBCRuujZk04Qdj2ZXKj9TvGE5fFjMPf+oAI75yhHlnSsblOGTA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2752,8 +2752,8 @@ "@babel/plugin-syntax-dynamic-import": "7.8.3", "@babel/plugin-transform-async-to-generator": "^7.18.6", "@babel/preset-env": "^7.24.4", - "@c8y/client": "1021.4.3", - "@c8y/options": "1021.4.3", + "@c8y/client": "1021.5.0", + "@c8y/options": "1021.5.0", "@inquirer/prompts": "5.3.7", "@schematics/angular": "^18.2.0", "angular-gettext-tools": "git+https://github.com/rubenv/angular-gettext-tools.git#5a20d5fe2ad768bfd0cac18259b4986346061eda", @@ -3007,9 +3007,9 @@ } }, "node_modules/@c8y/ngx-components": { - "version": "1021.4.3", - "resolved": "https://registry.npmjs.org/@c8y/ngx-components/-/ngx-components-1021.4.3.tgz", - "integrity": "sha512-cxMandMmKz4uqdvQMcyZl9lXNNRHJGWLNta0KkWG0MrhurdAl3Zo5ghr9VpDZzETL2pEddK4jnpTJvm2hUK2PA==", + "version": "1021.5.0", + "resolved": "https://registry.npmjs.org/@c8y/ngx-components/-/ngx-components-1021.5.0.tgz", + "integrity": "sha512-jpQpYoMCs+sqDzsiWtIJT5WIUbkx+8Qp4NKqfzLu189ux5CC2Z10HkmkNnCJUUgP/mtmuliDjaTWWSyGrGzvMw==", "license": "Apache-2.0", "dependencies": { "@ngx-formly/core": "6.1.3", @@ -3036,26 +3036,26 @@ "@angular/platform-browser": "^18.2.0", "@angular/router": "^18.2.0", "@angular/upgrade": "^18.2.0", - "@c8y/bootstrap": "1021.4.3", - "@c8y/client": "1021.4.3", + "@c8y/bootstrap": "1021.5.0", + "@c8y/client": "1021.5.0", "leaflet": "^1.7.1", "rxjs": "^7.4.0" } }, "node_modules/@c8y/options": { - "version": "1021.4.3", - "resolved": "https://registry.npmjs.org/@c8y/options/-/options-1021.4.3.tgz", - "integrity": "sha512-hl/lu2Avra/eLXWX0p3xeKj1AoHl94sHmuy70qHtU4LcSLlxUhWw1moyTzhG9LDPrK8sk/AqLG5Mpi1Yl1P/3g==", + "version": "1021.5.0", + "resolved": "https://registry.npmjs.org/@c8y/options/-/options-1021.5.0.tgz", + "integrity": "sha512-6o7CHbr5wmwTeV9ysBPnvWKzGM/0I7KxbMzhkRUHwI73Eas6WSQ/62cC79iZUU9LnlzgMPO5lAFEplq6fAKB5w==", "dev": true, "license": "Apache-2.0", "peerDependencies": { - "@c8y/client": "1021.4.3" + "@c8y/client": "1021.5.0" } }, "node_modules/@c8y/style": { - "version": "1021.4.3", - "resolved": "https://registry.npmjs.org/@c8y/style/-/style-1021.4.3.tgz", - "integrity": "sha512-7Ql3Ak9alVxjvM3j49Tws29cnEULBmr70TAkwXuDCdmcZosPJUJ9eZ/EfPyUCux+pHBD1mrYWmSXqHk3Q8sunQ==", + "version": "1021.5.0", + "resolved": "https://registry.npmjs.org/@c8y/style/-/style-1021.5.0.tgz", + "integrity": "sha512-7dq/XO7CY1rpPkcbtdknKf3bgvouME2yPoM7WiQF+e1TQ6CVJGiq0fwCRRn9LY0KSrr/1wMXBdILmA/RnLHusg==", "license": "Apache-2.0", "dependencies": { "@fontsource/public-sans": "^5.0.18" @@ -5359,9 +5359,9 @@ } }, "node_modules/@types/node": { - "version": "22.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", - "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", + "version": "22.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.1.tgz", + "integrity": "sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==", "dev": true, "license": "MIT", "dependencies": { @@ -12160,9 +12160,9 @@ } }, "node_modules/node-gyp-build": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.3.tgz", - "integrity": "sha512-EMS95CMJzdoSKoIiXo8pxKoL8DYxwIZXYlLmgPb8KUv794abpnLK6ynsCAWNliOjREKruYKdzbh76HHYUHX7nw==", + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "dev": true, "license": "MIT", "optional": true, diff --git a/package.json b/package.json index 19a7390..149820e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tutorial", - "version": "1021.4.3", + "version": "1021.5.0", "scripts": { "ng": "ng", "start": "ng serve", @@ -19,10 +19,10 @@ "@angular/platform-browser": "^18.2.0", "@angular/platform-browser-dynamic": "^18.2.0", "@angular/router": "^18.2.0", - "@c8y/bootstrap": "1021.4.3", - "@c8y/client": "1021.4.3", - "@c8y/ngx-components": "1021.4.3", - "@c8y/style": "1021.4.3", + "@c8y/bootstrap": "1021.5.0", + "@c8y/client": "1021.5.0", + "@c8y/ngx-components": "1021.5.0", + "@c8y/style": "1021.5.0", "leaflet": "1.9.4", "ngx-bootstrap": "18.0.0", "rxjs": "^7.8.1", @@ -33,8 +33,8 @@ "@angular-devkit/build-angular": "^18.2.10", "@angular/cli": "^18.2.10", "@angular/compiler-cli": "^18.2.0", - "@c8y/devkit": "1021.4.3", - "@c8y/options": "1021.4.3", + "@c8y/devkit": "1021.5.0", + "@c8y/options": "1021.5.0", "@types/jasmine": "~5.1.0", "jasmine-core": "~5.2.0", "karma": "~6.4.0", diff --git a/src/hooks/service/client/basic-view.component.html b/src/hooks/service/client/basic-view.component.html new file mode 100644 index 0000000..d627b7f --- /dev/null +++ b/src/hooks/service/client/basic-view.component.html @@ -0,0 +1,44 @@ +Sharing a service between plugins +
+

+ A service instance can be used by components that need to communicate to each other or share a + common state. If these two components originate from different code bases, e.g. are deployed via + plugin-ins, then + hookService + comes as a way for such a shared service to be injected. The service interface might be declared + in a shared library known at compile time. +

+

+ This example showcases two component instances that share a counter service. The counter service + interface is declared in the + counder.model + module which may be declared in a common library shared between plug-ins in a real world case. + The + CounterHookModule + leverages + hookService + to inject a service instance. This can happen in a plugin or in a shell application. +

+
+
+
+
+
+

Component A

+
+
+ +
+
+
+
+
+
+

Component B

+
+
+ +
+
+
+
diff --git a/src/hooks/service/client/basic-view.component.ts b/src/hooks/service/client/basic-view.component.ts new file mode 100644 index 0000000..98025dc --- /dev/null +++ b/src/hooks/service/client/basic-view.component.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; +import { CoreModule } from '@c8y/ngx-components'; + +/** + * This is the component that hosts the Service demo view. + */ +@Component({ + selector: 'tut-basic-component-hook-view', + templateUrl: './basic-view.component.html', + standalone: true, + imports: [CoreModule] +}) +export class BasicViewComponent {} diff --git a/src/hooks/service/client/counter.component.html b/src/hooks/service/client/counter.component.html new file mode 100644 index 0000000..8e02196 --- /dev/null +++ b/src/hooks/service/client/counter.component.html @@ -0,0 +1,15 @@ +

+ Hello there! I am a simple component added from a plugin by + hookComponent + . I use a shared counter service that has been provided in another plugin and injected by + hookService + . +

+ diff --git a/src/hooks/service/client/counter.component.ts b/src/hooks/service/client/counter.component.ts new file mode 100644 index 0000000..58074ae --- /dev/null +++ b/src/hooks/service/client/counter.component.ts @@ -0,0 +1,21 @@ +import { Component } from '@angular/core'; +import { ServiceRegistry } from '@c8y/ngx-components'; +import { ICounterService } from '../counter/counter.model'; + +@Component({ + selector: 'tut-counter-component', + templateUrl: './counter.component.html', + standalone: true +}) +export class CounterComponent { + counter: ICounterService; + + constructor(registry: ServiceRegistry) { + /** + * To retrieve an instance of a service injected by `hookService` you can use ServiceRegistry.get(key) method. + * It will return all injected service instances in a type-safe manner if there is a typed extension key declared. + * For an example of such declaration check the declarations in `counter/counter.model.ts`. + */ + this.counter = registry.get('counter').at(0); + } +} diff --git a/src/hooks/service/client/index.ts b/src/hooks/service/client/index.ts new file mode 100644 index 0000000..c7527c7 --- /dev/null +++ b/src/hooks/service/client/index.ts @@ -0,0 +1,3 @@ +export * from './basic-view.component'; +export * from './counter.component'; +export * from './service-hook.module'; diff --git a/src/hooks/service/client/service-hook.module.ts b/src/hooks/service/client/service-hook.module.ts new file mode 100644 index 0000000..0dd4c40 --- /dev/null +++ b/src/hooks/service/client/service-hook.module.ts @@ -0,0 +1,29 @@ +import { NgModule } from '@angular/core'; +import { NavigatorNode, hookComponent, hookNavigator, hookRoute } from '@c8y/ngx-components'; + +@NgModule({ + providers: [ + /* Hook the Service hook demo view */ + hookRoute({ + path: 'hooks/service', + loadComponent: () => import('./basic-view.component').then(m => m.BasicViewComponent) + }), + hookNavigator( + new NavigatorNode({ + priority: 40, + path: 'hooks/service', + icon: 'gears', + label: 'Service', + parent: 'Hooks' + }) + ), + /* Hook a client component for the service provided via `hookService` */ + hookComponent({ + id: 'counter.component', + label: 'Counter component', + description: 'This component can count', + loadComponent: () => import('./counter.component').then(m => m.CounterComponent) + }) + ] +}) +export class ServiceHookModule {} diff --git a/src/hooks/service/counter/counter.model.ts b/src/hooks/service/counter/counter.model.ts new file mode 100644 index 0000000..3424139 --- /dev/null +++ b/src/hooks/service/counter/counter.model.ts @@ -0,0 +1,43 @@ +/** + * Declare the contract of the service that will be injected via `hookService` in an interface. + * This interface will be used also to bind a type to the key used for providing and retrieving the service. + * This interface should be declared in a module that is shared between modules that use the service and those that implement and inject it. + */ +export interface ICounterService { + /** + * Current counter state. + */ + counter: number; + /** + * Increment counter value. + */ + count: () => void; +} + +declare global { + /** + * The `CumulocityServiceRegistry` namespaces is declared in `@c8y/ngx-components` as part of the global scope. + * This allows you to augment the service registry by adding your typed extension keys. + */ + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace CumulocityServiceRegistry { + interface ExtensionKeys { + /** + * The extension key used for injecting and retrieving hooked services. + * To provide a service with a key provide the service using `hookService`: + * + * ```typescript + * @NgModule({ + * providers: [hookService('counter', CounterService)] + * }) + * ``` + * + * In your client code you can use `ServiceRegistry` to retrieve an instance of the injected service: + * ```typescript + * ServiceRegistry.get(key) + * ``` + */ + counter: ICounterService; + } + } +} diff --git a/src/hooks/service/counter/counter.module.ts b/src/hooks/service/counter/counter.module.ts new file mode 100644 index 0000000..5e3b0f6 --- /dev/null +++ b/src/hooks/service/counter/counter.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { hookService } from '@c8y/ngx-components'; +import { CounterService } from './counter.service'; + +@NgModule({ + /** + * To provide a service using `hookService`, you pass a key that clients will use to retrieve the service instance. + * By extending the `ExtensionKeys` interface in the `CumulocityServiceRegistry` namespace, you declare the key for your service. + * `hookService` then enforces type safety, ensuring only services that implement the corresponding interface can be provided for that key. + */ + providers: [hookService('counter', CounterService)] +}) +export class CounterHookModule {} diff --git a/src/hooks/service/counter/counter.service.ts b/src/hooks/service/counter/counter.service.ts new file mode 100644 index 0000000..fee16e7 --- /dev/null +++ b/src/hooks/service/counter/counter.service.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@angular/core'; +import { ICounterService } from './counter.model'; + +@Injectable({ providedIn: 'root' }) +export class CounterService implements ICounterService { + counter = 0; + count() { + this.counter++; + } +} diff --git a/src/hooks/service/service-hook-codex-sample.module.ts b/src/hooks/service/service-hook-codex-sample.module.ts new file mode 100644 index 0000000..158cbe5 --- /dev/null +++ b/src/hooks/service/service-hook-codex-sample.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { ServiceHookModule } from './client/service-hook.module'; +import { CounterHookModule } from './counter/counter.module'; + +@NgModule({ + imports: [ServiceHookModule, CounterHookModule] +}) +/** + * `codex-tutorial-example` component supports a single module only, hence the module providing the service (CounterHookModule) + * and the one consuming the service (ServiceHookModule) need to be combined in a single module. + * In the general case, both modules above can be added as remotes separately. + */ +export class ServiceHookCodexSampleModule {}