@c8y/tutorial 1021.3.1 → 1021.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { ConfigurationOptions } from '@c8y/devkit';
2
2
  import { DefinePlugin } from 'webpack';
3
- import { author, description, version, name } from './package.json';
3
+ import { author, description, name, version } from './package.json';
4
4
 
5
5
  export default {
6
6
  runTime: {
@@ -261,6 +261,13 @@ export default {
261
261
  description: 'A sample for wizard hook.',
262
262
  scope: 'self'
263
263
  },
264
+ {
265
+ name: 'Service hook Codex sample',
266
+ module: 'ServiceHookCodexSampleModule',
267
+ path: './src/hooks/service/service-hook-codex-sample.module.ts',
268
+ description: 'A sample for hookService.',
269
+ scope: 'self'
270
+ },
264
271
  {
265
272
  name: 'Stepper',
266
273
  module: 'StepperHookModule',
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@c8y/tutorial",
3
- "version": "1021.3.1",
3
+ "version": "1021.6.0",
4
4
  "description": "This package is used to scaffold a tutorial for Cumulocity IoT Web SDK which explains a lot of concepts.",
5
5
  "dependencies": {
6
- "@c8y/style": "1021.3.1",
7
- "@c8y/ngx-components": "1021.3.1",
8
- "@c8y/client": "1021.3.1",
9
- "@c8y/bootstrap": "1021.3.1",
6
+ "@c8y/style": "1021.6.0",
7
+ "@c8y/ngx-components": "1021.6.0",
8
+ "@c8y/client": "1021.6.0",
9
+ "@c8y/bootstrap": "1021.6.0",
10
10
  "@angular/cdk": "^18.2.10",
11
11
  "ngx-bootstrap": "18.0.0",
12
12
  "leaflet": "1.9.4",
13
13
  "rxjs": "^7.8.1"
14
14
  },
15
15
  "devDependencies": {
16
- "@c8y/options": "1021.3.1",
17
- "@c8y/devkit": "1021.3.1"
16
+ "@c8y/options": "1021.6.0",
17
+ "@c8y/devkit": "1021.6.0"
18
18
  },
19
19
  "peerDependencies": {
20
20
  "@angular/common": ">=18 <19"
@@ -0,0 +1,10 @@
1
+ import { Component } from '@angular/core';
2
+ import { CellRendererContext } from '@c8y/ngx-components';
3
+
4
+ @Component({
5
+ template: ` {{ context.value | c8yDate }} `,
6
+ selector: 'c8y-last-updated-cell-renderer'
7
+ })
8
+ export class LastUpdatedCellRendererComponent {
9
+ constructor(public context: CellRendererContext) {}
10
+ }
@@ -0,0 +1,100 @@
1
+ import { FormGroup } from '@angular/forms';
2
+ import { BaseColumn, ColumnConfig, gettext } from '@c8y/ngx-components';
3
+ import { LastUpdatedCellRendererComponent } from './last-updated.cell-renderer.component';
4
+
5
+ /**
6
+ * Defines a class for custom Last updated date column.
7
+ * Implements `Column` interface and sets basic properties, as well as custom components.
8
+ */
9
+ export class LastUpdatedDataGridColumn extends BaseColumn {
10
+ constructor(initialColumnConfig?: ColumnConfig) {
11
+ super(initialColumnConfig);
12
+ this.name = 'lastUpdated';
13
+ this.path = 'lastUpdated';
14
+ this.header = 'Last updated';
15
+
16
+ this.cellRendererComponent = LastUpdatedCellRendererComponent;
17
+
18
+ this.filterable = true;
19
+
20
+ this.filteringConfig = {
21
+ /**
22
+ * When using Formly schema, filter chips can be automatically generated by the
23
+ * data-grid library based on the filtering schema definition. You do not need
24
+ * to manually implement the chip generation. The Formly field definitions provided
25
+ * in the `filteringConfig` will automatically be transformed into chips by the
26
+ * data-grid system.
27
+ */
28
+ fields: [
29
+ {
30
+ type: 'object',
31
+ key: 'lastUpdated',
32
+ templateOptions: {
33
+ label: gettext('Show items last updated')
34
+ },
35
+ fieldGroup: [
36
+ {
37
+ type: 'date-time',
38
+ key: 'after',
39
+ templateOptions: {
40
+ label: gettext('from')
41
+ },
42
+ expressionProperties: {
43
+ 'templateOptions.maxDate': (model: any) => model?.before
44
+ }
45
+ },
46
+ {
47
+ type: 'date-time',
48
+ key: 'before',
49
+ templateOptions: {
50
+ label: gettext('to')
51
+ },
52
+ expressionProperties: {
53
+ 'templateOptions.minDate': (model: any) => model?.after
54
+ }
55
+ }
56
+ ],
57
+ validators: {
58
+ atLeastOneFilled: {
59
+ expression: (formGroup: any) => {
60
+ const after = formGroup.get('after').value;
61
+ const before = formGroup.get('before').value;
62
+ return after || before;
63
+ }
64
+ }
65
+ }
66
+ }
67
+ ],
68
+ formGroup: new FormGroup({}),
69
+ getFilter: model => {
70
+ const filter: any = {};
71
+ const dates = model?.lastUpdated;
72
+ if (dates?.after || dates?.before) {
73
+ filter.__and = [];
74
+ if (dates?.after) {
75
+ const after = this.formatDate(dates.after);
76
+ filter.__and.push({
77
+ 'lastUpdated.date': { __gt: after }
78
+ });
79
+ }
80
+ if (dates?.before) {
81
+ const before = this.formatDate(dates.before);
82
+ filter.__and.push({
83
+ 'lastUpdated.date': { __lt: before }
84
+ });
85
+ }
86
+ }
87
+ return filter;
88
+ }
89
+ };
90
+
91
+ this.sortable = true;
92
+ this.sortingConfig = {
93
+ pathSortingConfigs: [{ path: 'lastUpdated.date' }]
94
+ };
95
+ }
96
+
97
+ protected formatDate(dateToFormat: string): string {
98
+ return new Date(dateToFormat).toISOString();
99
+ }
100
+ }
@@ -1,6 +1,5 @@
1
- import { CommonModule } from '@angular/common';
2
1
  import { NgModule } from '@angular/core';
3
- import { hookNavigator, hookRoute, NavigatorNode } from '@c8y/ngx-components';
2
+ import { CommonModule, hookNavigator, hookRoute, NavigatorNode } from '@c8y/ngx-components';
4
3
  import { ServerGridActionControlsModule } from './server-grid-action-controls.module';
5
4
 
6
5
  @NgModule({
@@ -10,6 +10,8 @@ import {
10
10
  Pagination
11
11
  } from '@c8y/ngx-components';
12
12
 
13
+ import { assign, get, identity } from 'lodash-es';
14
+ import { LastUpdatedDataGridColumn } from './last-updated-data-grid-column/last-updated.data-grid-column';
13
15
  import { TypeDataGridColumn } from './type-data-grid-column/type.data-grid-column';
14
16
 
15
17
  /** Model for custom type filtering form. */
@@ -59,7 +61,8 @@ export class ServerGridExampleService {
59
61
  filterable: true,
60
62
  sortable: true
61
63
  },
62
- new TypeDataGridColumn()
64
+ new TypeDataGridColumn(),
65
+ new LastUpdatedDataGridColumn()
63
66
  ];
64
67
 
65
68
  return columns;
@@ -177,43 +180,6 @@ export class ServerGridExampleService {
177
180
  return { icon, label };
178
181
  }
179
182
 
180
- /** Returns a query object for given settings of filtering by type. */
181
- getTypeQuery(model: TypeFilteringModel): any {
182
- let query: any = {};
183
-
184
- if (model.group) {
185
- query = this.queriesUtil.addOrFilter(query, { type: 'c8y_DeviceGroup' });
186
- }
187
-
188
- if (model.device) {
189
- query = this.queriesUtil.addOrFilter(query, { __has: 'c8y_IsDevice' });
190
- }
191
-
192
- if (model.smartRule) {
193
- query = this.queriesUtil.addOrFilter(query, {
194
- type: { __in: ['c8y_SmartRule', 'c8y_PrivateSmartRule'] }
195
- });
196
- }
197
-
198
- if (model.dashboard) {
199
- query = this.queriesUtil.addOrFilter(query, {
200
- type: { __has: 'c8y_Dashboard' }
201
- });
202
- }
203
-
204
- if (model.file) {
205
- query = this.queriesUtil.addOrFilter(query, {
206
- type: { __has: 'c8y_IsBinary' }
207
- });
208
- }
209
-
210
- if (model.application) {
211
- query = this.queriesUtil.addOrFilter(query, { type: 'c8y_Application_*' });
212
- }
213
-
214
- return query;
215
- }
216
-
217
183
  /** Returns filters for given columns and pagination setup. */
218
184
  private getFilters(columns: Column[], pagination: Pagination) {
219
185
  return {
@@ -232,10 +198,11 @@ export class ServerGridExampleService {
232
198
  }
233
199
 
234
200
  /** Returns a query object based on columns setup. */
235
- private getQueryObj(columns: Column[]): any {
201
+ private getQueryObj(columns: Column[], defaultFilter = {}): any {
236
202
  return transform(columns, (query, column) => this.addColumnQuery(query, column), {
237
203
  __filter: {},
238
- __orderby: []
204
+ __orderby: [],
205
+ ...defaultFilter
239
206
  });
240
207
  }
241
208
 
@@ -251,7 +218,17 @@ export class ServerGridExampleService {
251
218
 
252
219
  // in the case of custom filtering form, we're storing the query in `externalFilterQuery.query`
253
220
  if (column.externalFilterQuery) {
254
- query = this.queriesUtil.addAndFilter(query, column.externalFilterQuery.query);
221
+ const getFilter = column.filteringConfig.getFilter || identity;
222
+ const queryObj = getFilter(column.externalFilterQuery);
223
+
224
+ if (queryObj.__or) {
225
+ query.__filter.__and = query.__filter.__and || [];
226
+ query.__filter.__and.push(queryObj);
227
+ } else if (queryObj.__and && get(query, '__filter.__and')) {
228
+ queryObj.__and.map(obj => query.__filter.__and.push(obj));
229
+ } else {
230
+ assign(query.__filter, queryObj);
231
+ }
255
232
  }
256
233
  }
257
234
 
@@ -1,47 +1,88 @@
1
1
  import { Type } from '@angular/core';
2
- import { Column, ColumnDataType, SortOrder, FilterPredicateFunction } from '@c8y/ngx-components';
3
- import { TypeHeaderCellRendererComponent } from './type.header-cell-renderer.component';
2
+ import { BaseColumn, ColumnConfig, PartialFilterChipGenerationType } from '@c8y/ngx-components';
4
3
  import { TypeCellRendererComponent } from './type.cell-renderer.component';
5
4
  import { TypeFilteringFormRendererComponent } from './type.filtering-form-renderer.component';
5
+ import { TypeHeaderCellRendererComponent } from './type.header-cell-renderer.component';
6
+
7
+ const FILTER_TYPES = [
8
+ { key: 'group', label: 'Group' },
9
+ { key: 'device', label: 'Device' },
10
+ { key: 'smartRule', label: 'Smart Rule' },
11
+ { key: 'dashboard', label: 'Dashboard' },
12
+ { key: 'file', label: 'File' },
13
+ { key: 'application', label: 'Application' }
14
+ ];
6
15
 
7
16
  /**
8
- * Defines a class for custom Type column.
9
- * Implements `Column` interface and sets basic properties, as well as custom components.
17
+ * Defines a custom Type column with custom filtering form and chips generation.
10
18
  */
11
- export class TypeDataGridColumn implements Column {
12
- name: string;
13
- path?: string;
14
- header?: string;
15
- dataType?: ColumnDataType;
16
-
17
- visible?: boolean;
18
- positionFixed?: boolean;
19
- gridTrackSize?: string;
19
+ export class TypeDataGridColumn extends BaseColumn {
20
+ readonly name = 'type';
21
+ readonly header = 'Type';
20
22
 
21
- headerCSSClassName?: string | string[];
22
- headerCellRendererComponent?: Type<any>;
23
+ headerCellRendererComponent: Type<any> = TypeHeaderCellRendererComponent;
24
+ cellRendererComponent: Type<any> = TypeCellRendererComponent;
25
+ sortable = false;
26
+ filterable = true;
27
+ filteringFormRendererComponent: Type<any> = TypeFilteringFormRendererComponent;
23
28
 
24
- cellCSSClassName?: string | string[];
25
- cellRendererComponent?: Type<any>;
29
+ constructor(initialColumnConfig?: ColumnConfig) {
30
+ super(initialColumnConfig);
26
31
 
27
- sortable?: boolean;
28
- sortOrder?: SortOrder;
32
+ // Set the custom filtering configuration
33
+ this.filteringConfig = {
34
+ /**
35
+ * Generates filter chips based on the selected filters in the model.
36
+ * Each chip represents a filter and provides a way for users to visualize
37
+ * and remove the applied filters.
38
+ *
39
+ * @param model An object with defined structure (e.g. by schema).
40
+ * @returns return an array of partial filter chips with required properties 'displayValue' and 'value'.
41
+ */
42
+ generateChips: (model): PartialFilterChipGenerationType[] => {
43
+ const chips = [];
29
44
 
30
- filterable?: boolean;
31
- filteringFormRendererComponent?: Type<any>;
32
- filterPredicate?: string | FilterPredicateFunction;
33
- externalFilterQuery?: string | object;
45
+ FILTER_TYPES.forEach(type => {
46
+ if (model[type.key]) {
47
+ chips.push({
48
+ displayValue: type.label,
49
+ value: type.key,
50
+ remove: () => {
51
+ delete model[type.key];
52
+ return {
53
+ externalFilterQuery: { ...model },
54
+ columnName: this.name
55
+ };
56
+ }
57
+ });
58
+ }
59
+ });
34
60
 
35
- constructor() {
36
- this.name = 'type';
37
- this.header = 'Type';
61
+ return chips;
62
+ },
63
+ /**
64
+ * Transforms a filtering config model (e.g. coming from schema form component) to a query object.
65
+ * However, using schema form component is not necessary.
66
+ * Model can be defined arbitrarily but must converted to a valid query object.
67
+ * @param model An object with defined structure (e.g. by schema).
68
+ * @returns A query object to be used to generate a query string (QueryUtils).
69
+ */
70
+ getFilter: model => {
71
+ const filter: any = {};
72
+ const ors = [];
38
73
 
39
- this.headerCellRendererComponent = TypeHeaderCellRendererComponent;
40
- this.cellRendererComponent = TypeCellRendererComponent;
74
+ if (model.group) ors.push({ type: 'c8y_DeviceGroup' });
75
+ if (model.device) ors.push({ __has: 'c8y_IsDevice' });
76
+ if (model.smartRule)
77
+ ors.push({ type: { __in: ['c8y_SmartRule', 'c8y_PrivateSmartRule'] } });
78
+ if (model.dashboard) ors.push({ type: { __has: 'c8y_Dashboard' } });
79
+ if (model.file) ors.push({ type: { __has: 'c8y_IsBinary' } });
80
+ if (model.application) ors.push({ type: 'c8y_Application_*' });
41
81
 
42
- this.filterable = true;
43
- this.filteringFormRendererComponent = TypeFilteringFormRendererComponent;
82
+ if (ors.length) filter.__or = ors;
44
83
 
45
- this.sortable = false;
84
+ return filter;
85
+ }
86
+ };
46
87
  }
47
88
  }
@@ -1,6 +1,6 @@
1
- import { Component, Inject } from '@angular/core';
1
+ import { Component } from '@angular/core';
2
2
  import { FilteringFormRendererContext, FormsModule } from '@c8y/ngx-components';
3
- import { ServerGridExampleService, TypeFilteringModel } from '../server-grid-example.service';
3
+ import { TypeFilteringModel } from '../server-grid-example.service';
4
4
 
5
5
  /**
6
6
  * This is the example component for custom filtering form.
@@ -79,12 +79,9 @@ import { ServerGridExampleService, TypeFilteringModel } from '../server-grid-exa
79
79
  export class TypeFilteringFormRendererComponent {
80
80
  model: TypeFilteringModel;
81
81
 
82
- constructor(
83
- public context: FilteringFormRendererContext,
84
- @Inject(ServerGridExampleService) public service: ServerGridExampleService
85
- ) {
82
+ constructor(public context: FilteringFormRendererContext) {
86
83
  // restores the settings from current column setup
87
- this.model = (this.context.property.externalFilterQuery || {}).model || {};
84
+ this.model = this.context.property.externalFilterQuery || {};
88
85
  }
89
86
 
90
87
  /**
@@ -94,10 +91,7 @@ export class TypeFilteringFormRendererComponent {
94
91
  */
95
92
  applyFilter() {
96
93
  this.context.applyFilter({
97
- externalFilterQuery: {
98
- model: this.model,
99
- query: this.service.getTypeQuery(this.model)
100
- }
94
+ externalFilterQuery: { ...this.model }
101
95
  });
102
96
  }
103
97
 
@@ -0,0 +1,44 @@
1
+ <c8y-title>Sharing a service between plugins</c8y-title>
2
+ <div class="card-block">
3
+ <p>
4
+ A service instance can be used by components that need to communicate to each other or share a
5
+ common state. If these two components originate from different code bases, e.g. are deployed via
6
+ plugin-ins, then
7
+ <code>hookService</code>
8
+ comes as a way for such a shared service to be injected. The service interface might be declared
9
+ in a shared library known at compile time.
10
+ </p>
11
+ <p>
12
+ This example showcases two component instances that share a counter service. The counter service
13
+ interface is declared in the
14
+ <code>counder.model</code>
15
+ module which may be declared in a common library shared between plug-ins in a real world case.
16
+ The
17
+ <code>CounterHookModule</code>
18
+ leverages
19
+ <code>hookService</code>
20
+ to inject a service instance. This can happen in a plugin or in a shell application.
21
+ </p>
22
+ </div>
23
+ <div class="card-group">
24
+ <div class="col-md-6">
25
+ <div class="card">
26
+ <div class="card-header separator">
27
+ <p class="card-title">Component A</p>
28
+ </div>
29
+ <div class="card-block">
30
+ <c8y-dynamic-component componentId="counter.component"></c8y-dynamic-component>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ <div class="col-md-6">
35
+ <div class="card">
36
+ <div class="card-header separator">
37
+ <p class="card-title">Component B</p>
38
+ </div>
39
+ <div class="card-block">
40
+ <c8y-dynamic-component componentId="counter.component"></c8y-dynamic-component>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
@@ -0,0 +1,13 @@
1
+ import { Component } from '@angular/core';
2
+ import { CoreModule } from '@c8y/ngx-components';
3
+
4
+ /**
5
+ * This is the component that hosts the Service demo view.
6
+ */
7
+ @Component({
8
+ selector: 'tut-basic-component-hook-view',
9
+ templateUrl: './basic-view.component.html',
10
+ standalone: true,
11
+ imports: [CoreModule]
12
+ })
13
+ export class BasicViewComponent {}
@@ -0,0 +1,15 @@
1
+ <p>
2
+ Hello there! I am a simple component added from a plugin by
3
+ <code>hookComponent</code>
4
+ . I use a shared counter service that has been provided in another plugin and injected by
5
+ <code>hookService</code>
6
+ .
7
+ </p>
8
+ <button
9
+ class="btn btn-default m-t-16"
10
+ type="button"
11
+ (click)="counter.count()"
12
+ >
13
+ Click to increment counter
14
+ <span class="badge badge-system">{{ counter.counter }}</span>
15
+ </button>
@@ -0,0 +1,21 @@
1
+ import { Component } from '@angular/core';
2
+ import { ServiceRegistry } from '@c8y/ngx-components';
3
+ import { ICounterService } from '../counter/counter.model';
4
+
5
+ @Component({
6
+ selector: 'tut-counter-component',
7
+ templateUrl: './counter.component.html',
8
+ standalone: true
9
+ })
10
+ export class CounterComponent {
11
+ counter: ICounterService;
12
+
13
+ constructor(registry: ServiceRegistry) {
14
+ /**
15
+ * To retrieve an instance of a service injected by `hookService` you can use ServiceRegistry.get(key) method.
16
+ * It will return all injected service instances in a type-safe manner if there is a typed extension key declared.
17
+ * For an example of such declaration check the declarations in `counter/counter.model.ts`.
18
+ */
19
+ this.counter = registry.get('counter').at(0);
20
+ }
21
+ }
@@ -0,0 +1,3 @@
1
+ export * from './basic-view.component';
2
+ export * from './counter.component';
3
+ export * from './service-hook.module';
@@ -0,0 +1,29 @@
1
+ import { NgModule } from '@angular/core';
2
+ import { NavigatorNode, hookComponent, hookNavigator, hookRoute } from '@c8y/ngx-components';
3
+
4
+ @NgModule({
5
+ providers: [
6
+ /* Hook the Service hook demo view */
7
+ hookRoute({
8
+ path: 'hooks/service',
9
+ loadComponent: () => import('./basic-view.component').then(m => m.BasicViewComponent)
10
+ }),
11
+ hookNavigator(
12
+ new NavigatorNode({
13
+ priority: 40,
14
+ path: 'hooks/service',
15
+ icon: 'gears',
16
+ label: 'Service',
17
+ parent: 'Hooks'
18
+ })
19
+ ),
20
+ /* Hook a client component for the service provided via `hookService` */
21
+ hookComponent({
22
+ id: 'counter.component',
23
+ label: 'Counter component',
24
+ description: 'This component can count',
25
+ loadComponent: () => import('./counter.component').then(m => m.CounterComponent)
26
+ })
27
+ ]
28
+ })
29
+ export class ServiceHookModule {}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Declare the contract of the service that will be injected via `hookService` in an interface.
3
+ * This interface will be used also to bind a type to the key used for providing and retrieving the service.
4
+ * This interface should be declared in a module that is shared between modules that use the service and those that implement and inject it.
5
+ */
6
+ export interface ICounterService {
7
+ /**
8
+ * Current counter state.
9
+ */
10
+ counter: number;
11
+ /**
12
+ * Increment counter value.
13
+ */
14
+ count: () => void;
15
+ }
16
+
17
+ declare global {
18
+ /**
19
+ * The `CumulocityServiceRegistry` namespaces is declared in `@c8y/ngx-components` as part of the global scope.
20
+ * This allows you to augment the service registry by adding your typed extension keys.
21
+ */
22
+ // eslint-disable-next-line @typescript-eslint/no-namespace
23
+ namespace CumulocityServiceRegistry {
24
+ interface ExtensionKeys {
25
+ /**
26
+ * The extension key used for injecting and retrieving hooked services.
27
+ * To provide a service with a key provide the service using `hookService`:
28
+ *
29
+ * ```typescript
30
+ * @NgModule({
31
+ * providers: [hookService('counter', CounterService)]
32
+ * })
33
+ * ```
34
+ *
35
+ * In your client code you can use `ServiceRegistry` to retrieve an instance of the injected service:
36
+ * ```typescript
37
+ * ServiceRegistry.get(key)
38
+ * ```
39
+ */
40
+ counter: ICounterService;
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,13 @@
1
+ import { NgModule } from '@angular/core';
2
+ import { hookService } from '@c8y/ngx-components';
3
+ import { CounterService } from './counter.service';
4
+
5
+ @NgModule({
6
+ /**
7
+ * To provide a service using `hookService`, you pass a key that clients will use to retrieve the service instance.
8
+ * By extending the `ExtensionKeys` interface in the `CumulocityServiceRegistry` namespace, you declare the key for your service.
9
+ * `hookService` then enforces type safety, ensuring only services that implement the corresponding interface can be provided for that key.
10
+ */
11
+ providers: [hookService('counter', CounterService)]
12
+ })
13
+ export class CounterHookModule {}
@@ -0,0 +1,10 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { ICounterService } from './counter.model';
3
+
4
+ @Injectable({ providedIn: 'root' })
5
+ export class CounterService implements ICounterService {
6
+ counter = 0;
7
+ count() {
8
+ this.counter++;
9
+ }
10
+ }
@@ -0,0 +1,13 @@
1
+ import { NgModule } from '@angular/core';
2
+ import { ServiceHookModule } from './client/service-hook.module';
3
+ import { CounterHookModule } from './counter/counter.module';
4
+
5
+ @NgModule({
6
+ imports: [ServiceHookModule, CounterHookModule]
7
+ })
8
+ /**
9
+ * `codex-tutorial-example` component supports a single module only, hence the module providing the service (CounterHookModule)
10
+ * and the one consuming the service (ServiceHookModule) need to be combined in a single module.
11
+ * In the general case, both modules above can be added as remotes separately.
12
+ */
13
+ export class ServiceHookCodexSampleModule {}
@@ -19,4 +19,14 @@
19
19
  [properties]="specifiedPackageVersionProperties"
20
20
  ></c8y-properties-list>
21
21
  <!-- /important -->
22
+
23
+ <!-- important -->
24
+ <c8y-properties-list
25
+ [title]="'Properties with no parse enabled'"
26
+ [data]="[]"
27
+ [emptyLabel]="'--'"
28
+ [noParse]="true"
29
+ [properties]="customPropertiesNoParse"
30
+ ></c8y-properties-list>
31
+ <!-- /important -->
22
32
  </div>
@@ -94,4 +94,25 @@ export class PropertiesListExampleComponent {
94
94
  key: 'custom'
95
95
  }
96
96
  ];
97
+
98
+ readonly customPropertiesNoParse: PropertiesListItem[] = [
99
+ {
100
+ label: 'String property',
101
+ key: 'string',
102
+ value: this.customData.string,
103
+ type: 'string'
104
+ },
105
+ {
106
+ label: 'Number property',
107
+ key: 'number',
108
+ value: this.customData.number,
109
+ type: 'string'
110
+ },
111
+ {
112
+ label: 'Array property',
113
+ key: 'array',
114
+ value: this.customData.array,
115
+ type: 'array'
116
+ }
117
+ ];
97
118
  }