@rs-x/core 0.4.11 → 0.4.12
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.
- package/dist/index.d.ts +183 -95
- package/dist/index.js +423 -90
- package/package.json +1 -1
- package/readme.md +472 -494
package/readme.md
CHANGED
|
@@ -2,25 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
Provides shared core functionality for the RS-X project:
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
- [Dependency Injection](#dependency-injection)
|
|
6
|
+
- [Deep Clone](#deep-clone)
|
|
7
|
+
- [Deep Equality](#deep-equality)
|
|
8
|
+
- [Guid Factory](#guid-factory)
|
|
9
|
+
- [Index Value Accessor](#index-value-accessor)
|
|
10
|
+
- [Singleton factory](#singleton-factory)
|
|
11
|
+
- [Error Log](#error-log)
|
|
12
|
+
- [WaitForEvent](#waitforevent)
|
|
13
13
|
|
|
14
14
|
## Dependency Injection
|
|
15
|
+
|
|
15
16
|
Implemented with [Inversify](https://github.com/inversify/InversifyJS).
|
|
16
17
|
|
|
17
18
|
The following aliases were added to make them consistent with the code style used throughout the project:
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
- `inject` renamed to `Inject`
|
|
21
|
+
- `multiInject` renamed to `MultiInject`
|
|
22
|
+
- `injectable` renamed to `Injectable`
|
|
23
|
+
- `unmanaged` renamed to `Unmanaged`
|
|
24
|
+
- `preDestroy` renamed to `PreDestroy`
|
|
24
25
|
|
|
25
26
|
In addition, the following extensions were added to Inversify:
|
|
26
27
|
|
|
@@ -93,11 +94,9 @@ Overrides an existing multi-inject list, removing any previous bindings for the
|
|
|
93
94
|
- Use this function when you want to completely replace the multi-inject service list.
|
|
94
95
|
- Ensures that `container.getAll(multiInjectToken)` returns only the new services without duplicates.
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
|
|
98
97
|
## Deep Clone
|
|
99
98
|
|
|
100
|
-
|
|
99
|
+
- Uses [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone) by default
|
|
101
100
|
- Falls back to [Lodash `cloneDeepWith`](https://lodash.com/docs/4.17.15#cloneDeepWith) for unsupported types
|
|
102
101
|
|
|
103
102
|
### Get an instance of the Deep clone service
|
|
@@ -107,10 +106,7 @@ You must load the core module into the injection container if you want
|
|
|
107
106
|
to use it.
|
|
108
107
|
|
|
109
108
|
```ts
|
|
110
|
-
import {
|
|
111
|
-
InjectionContainer,
|
|
112
|
-
RsXCoreModule
|
|
113
|
-
} from '@rs-x/core';
|
|
109
|
+
import { InjectionContainer, RsXCoreModule } from '@rs-x/core';
|
|
114
110
|
|
|
115
111
|
InjectionContainer.load(RsXCoreModule);
|
|
116
112
|
```
|
|
@@ -119,67 +115,65 @@ There are two ways to get an instance:
|
|
|
119
115
|
|
|
120
116
|
1. Using the injection container
|
|
121
117
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
118
|
+
```ts
|
|
119
|
+
import {
|
|
120
|
+
IDeepClone,
|
|
121
|
+
InjectionContainer,
|
|
122
|
+
RsXCoreInjectionTokens,
|
|
123
|
+
} from '@rs-x/core';
|
|
124
|
+
|
|
125
|
+
const deepClone: IDeepClone = InjectionContainer.get(
|
|
126
|
+
RsXCoreInjectionTokens.IDeepClone,
|
|
127
|
+
);
|
|
128
|
+
```
|
|
133
129
|
|
|
134
130
|
2. Using the `@Inject` decorator
|
|
135
131
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
IDeepClone,
|
|
139
|
-
Inject,
|
|
140
|
-
RsXCoreInjectionTokens
|
|
141
|
-
} from '@rs-x/core';
|
|
142
|
-
|
|
143
|
-
export class MyClass {
|
|
132
|
+
```ts
|
|
133
|
+
import { IDeepClone, Inject, RsXCoreInjectionTokens } from '@rs-x/core';
|
|
144
134
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
135
|
+
export class MyClass {
|
|
136
|
+
constructor(
|
|
137
|
+
@Inject(RsXCoreInjectionTokens.IDeepClone)
|
|
138
|
+
private readonly _deepClone: IDeepClone,
|
|
139
|
+
) {}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
151
142
|
|
|
152
143
|
The following example shows how to use deep clone service:
|
|
153
144
|
|
|
154
145
|
```ts
|
|
155
146
|
import {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
147
|
+
IDeepClone,
|
|
148
|
+
InjectionContainer,
|
|
149
|
+
printValue,
|
|
150
|
+
RsXCoreInjectionTokens,
|
|
151
|
+
RsXCoreModule,
|
|
161
152
|
} from '@rs-x/core';
|
|
162
153
|
|
|
163
154
|
// Load the core module into the injection container
|
|
164
155
|
InjectionContainer.load(RsXCoreModule);
|
|
165
|
-
const deepClone: IDeepClone = InjectionContainer.get(
|
|
156
|
+
const deepClone: IDeepClone = InjectionContainer.get(
|
|
157
|
+
RsXCoreInjectionTokens.IDeepClone,
|
|
158
|
+
);
|
|
166
159
|
|
|
167
160
|
export const run = (() => {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
161
|
+
const object = {
|
|
162
|
+
a: 10,
|
|
163
|
+
nested: {
|
|
164
|
+
b: 20,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
const clone = deepClone.clone(object);
|
|
168
|
+
|
|
169
|
+
console.log(`Clone is a copy of the cloned object: ${object !== clone}`);
|
|
170
|
+
console.log('Cloned object');
|
|
171
|
+
printValue(clone);
|
|
179
172
|
})();
|
|
180
173
|
```
|
|
181
174
|
|
|
182
175
|
**Output:**
|
|
176
|
+
|
|
183
177
|
```console
|
|
184
178
|
Running demo: demo/src/rs-x-core/deep-clone.ts
|
|
185
179
|
Clone is a copy of the cloned object: true
|
|
@@ -203,10 +197,7 @@ You must load the core module into the injection container if you want
|
|
|
203
197
|
to use it.
|
|
204
198
|
|
|
205
199
|
```ts
|
|
206
|
-
import {
|
|
207
|
-
InjectionContainer,
|
|
208
|
-
RsXCoreModule
|
|
209
|
-
} from '@rs-x/core';
|
|
200
|
+
import { InjectionContainer, RsXCoreModule } from '@rs-x/core';
|
|
210
201
|
|
|
211
202
|
InjectionContainer.load(RsXCoreModule);
|
|
212
203
|
```
|
|
@@ -215,75 +206,77 @@ There are two ways to get an instance:
|
|
|
215
206
|
|
|
216
207
|
1. Using the injection container
|
|
217
208
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const equalityService: IEqualityService = InjectionContainer.get(
|
|
226
|
-
RsXCoreInjectionTokens.IEqualityService
|
|
227
|
-
);
|
|
228
|
-
```
|
|
209
|
+
```ts
|
|
210
|
+
import {
|
|
211
|
+
IEqualityService,
|
|
212
|
+
InjectionContainer,
|
|
213
|
+
RsXCoreInjectionTokens,
|
|
214
|
+
} from '@rs-x/core';
|
|
229
215
|
|
|
230
|
-
|
|
216
|
+
const equalityService: IEqualityService = InjectionContainer.get(
|
|
217
|
+
RsXCoreInjectionTokens.IEqualityService,
|
|
218
|
+
);
|
|
219
|
+
```
|
|
231
220
|
|
|
232
|
-
|
|
233
|
-
import {
|
|
234
|
-
IEqualityService,
|
|
235
|
-
Inject,
|
|
236
|
-
RsXCoreInjectionTokens
|
|
237
|
-
} from '@rs-x/core';
|
|
238
|
-
|
|
239
|
-
export class MyClass {
|
|
221
|
+
2. Using the `@Inject` decorator
|
|
240
222
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
223
|
+
```ts
|
|
224
|
+
import {
|
|
225
|
+
IEqualityService,
|
|
226
|
+
Inject,
|
|
227
|
+
RsXCoreInjectionTokens,
|
|
228
|
+
} from '@rs-x/core';
|
|
229
|
+
|
|
230
|
+
export class MyClass {
|
|
231
|
+
constructor(
|
|
232
|
+
@Inject(RsXCoreInjectionTokens.IEqualityService)
|
|
233
|
+
private readonly _equalityService: IEqualityService,
|
|
234
|
+
) {}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
247
237
|
|
|
248
238
|
The following example shows how to use equality service
|
|
249
239
|
|
|
250
240
|
```ts
|
|
251
241
|
import {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
242
|
+
IEqualityService,
|
|
243
|
+
InjectionContainer,
|
|
244
|
+
printValue,
|
|
245
|
+
RsXCoreInjectionTokens,
|
|
246
|
+
RsXCoreModule,
|
|
257
247
|
} from '@rs-x/core';
|
|
258
248
|
|
|
259
249
|
// Load the core module into the injection container
|
|
260
250
|
InjectionContainer.load(RsXCoreModule);
|
|
261
|
-
const equalityService: IEqualityService = InjectionContainer.get(
|
|
251
|
+
const equalityService: IEqualityService = InjectionContainer.get(
|
|
252
|
+
RsXCoreInjectionTokens.IEqualityService,
|
|
253
|
+
);
|
|
262
254
|
|
|
263
255
|
export const run = (() => {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
256
|
+
const object1 = {
|
|
257
|
+
a: 10,
|
|
258
|
+
nested: {
|
|
259
|
+
b: 20,
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
const object2 = {
|
|
263
|
+
a: 10,
|
|
264
|
+
nested: {
|
|
265
|
+
b: 20,
|
|
266
|
+
},
|
|
267
|
+
};
|
|
276
268
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
269
|
+
printValue(object1);
|
|
270
|
+
console.log('is equal to');
|
|
271
|
+
printValue(object2);
|
|
280
272
|
|
|
281
|
-
|
|
282
|
-
|
|
273
|
+
const result = equalityService.isEqual(object1, object2);
|
|
274
|
+
console.log(`Result: ${result}`);
|
|
283
275
|
})();
|
|
284
276
|
```
|
|
285
277
|
|
|
286
278
|
**Output:**
|
|
279
|
+
|
|
287
280
|
```console
|
|
288
281
|
Running demo: demo/src/rs-x-core/equality-service.ts
|
|
289
282
|
{
|
|
@@ -313,10 +306,7 @@ You must load the core module into the injection container if you want
|
|
|
313
306
|
to use it.
|
|
314
307
|
|
|
315
308
|
```ts
|
|
316
|
-
import {
|
|
317
|
-
InjectionContainer,
|
|
318
|
-
RsXCoreModule
|
|
319
|
-
} from '@rs-x/core';
|
|
309
|
+
import { InjectionContainer, RsXCoreModule } from '@rs-x/core';
|
|
320
310
|
|
|
321
311
|
InjectionContainer.load(RsXCoreModule);
|
|
322
312
|
```
|
|
@@ -325,57 +315,55 @@ There are two ways to get an instance:
|
|
|
325
315
|
|
|
326
316
|
1. Using the injection container
|
|
327
317
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
318
|
+
```ts
|
|
319
|
+
import {
|
|
320
|
+
IGuidFactory,
|
|
321
|
+
InjectionContainer,
|
|
322
|
+
RsXCoreInjectionTokens,
|
|
323
|
+
} from '@rs-x/core';
|
|
324
|
+
|
|
325
|
+
const guidFactory: IGuidFactory = InjectionContainer.get(
|
|
326
|
+
RsXCoreInjectionTokens.IGuidFactory,
|
|
327
|
+
);
|
|
328
|
+
```
|
|
339
329
|
|
|
340
330
|
2. Using the `@Inject` decorator
|
|
341
331
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
IGuidFactory,
|
|
345
|
-
Inject,
|
|
346
|
-
RsXCoreInjectionTokens
|
|
347
|
-
} from '@rs-x/core';
|
|
348
|
-
|
|
349
|
-
export class MyClass {
|
|
332
|
+
```ts
|
|
333
|
+
import { IGuidFactory, Inject, RsXCoreInjectionTokens } from '@rs-x/core';
|
|
350
334
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
335
|
+
export class MyClass {
|
|
336
|
+
constructor(
|
|
337
|
+
@Inject(RsXCoreInjectionTokens.IGuidFactory)
|
|
338
|
+
private readonly _guidFactory: IGuidFactory,
|
|
339
|
+
) {}
|
|
340
|
+
}
|
|
341
|
+
```
|
|
357
342
|
|
|
358
343
|
The following example shows how to use the guid factory
|
|
359
344
|
|
|
360
345
|
```ts
|
|
361
346
|
import {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
347
|
+
IGuidFactory,
|
|
348
|
+
InjectionContainer,
|
|
349
|
+
RsXCoreInjectionTokens,
|
|
350
|
+
RsXCoreModule,
|
|
366
351
|
} from '@rs-x/core';
|
|
367
352
|
|
|
368
353
|
// Load the core module into the injection container
|
|
369
354
|
InjectionContainer.load(RsXCoreModule);
|
|
370
|
-
const guidFactory: IGuidFactory = InjectionContainer.get(
|
|
355
|
+
const guidFactory: IGuidFactory = InjectionContainer.get(
|
|
356
|
+
RsXCoreInjectionTokens.IGuidFactory,
|
|
357
|
+
);
|
|
371
358
|
|
|
372
359
|
export const run = (() => {
|
|
373
|
-
|
|
374
|
-
|
|
360
|
+
const guid = guidFactory.create();
|
|
361
|
+
console.log(`Created guid: ${guid}`);
|
|
375
362
|
})();
|
|
376
363
|
```
|
|
377
364
|
|
|
378
365
|
**Output:**
|
|
366
|
+
|
|
379
367
|
```console
|
|
380
368
|
Running demo: demo/src/rs-x-core/guid-factory.ts
|
|
381
369
|
Created guid 1f64aabb-a57e-42e7-9edf-71c24773c150
|
|
@@ -430,6 +418,7 @@ Normalizes access to object properties, array indices, map keys, and similar ind
|
|
|
430
418
|
### Members
|
|
431
419
|
|
|
432
420
|
### **priority**
|
|
421
|
+
|
|
433
422
|
**Type:** `number`
|
|
434
423
|
Defines the priority of the index value accessor. Higher numbers indicate higher priority when selecting which accessor to use.
|
|
435
424
|
|
|
@@ -531,14 +520,14 @@ Returns `true` if this index value accessor supports the given `(context, index)
|
|
|
531
520
|
The default `IIndexValueAccessor` implementation internally uses the following list of `IIndexValueAccessor` implementations.
|
|
532
521
|
The accessors are evaluated in order of **priority**, with higher-priority accessors being checked first:
|
|
533
522
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
523
|
+
- **`PropertyValueAccessor`** – accesses properties or fields on an object. Priority = 7
|
|
524
|
+
- **`MethodAccessor`** – accesses methods on an object. Priority = 6
|
|
525
|
+
- **`ArrayIndexAccessor`** – accesses array items. Priority = 5
|
|
526
|
+
- **`MapKeyccessor`** – accesses map items. Priority = 4
|
|
527
|
+
- **`SetKeyAccessor`** – accesses `Set` items. Priority = 3
|
|
528
|
+
- **`ObservableAccessor`** – accesses the latest value emitted by an `Observable`. Priority = 2
|
|
529
|
+
- **`PromiseAccessor`** – accesses the resolved value of a `Promise`. Priority = 1
|
|
530
|
+
- **`DatePropertyAccessor`** – accesses date-related properties. Priority = 0
|
|
542
531
|
|
|
543
532
|
The default accessor attempts to find the appropriate index value accessor for a given `(context, index)` pair and delegates the operation to it.
|
|
544
533
|
|
|
@@ -546,15 +535,12 @@ If no suitable index value accessor can be found, an `UnsupportedException` is t
|
|
|
546
535
|
|
|
547
536
|
### Get an instance of the Index Value Accessor Service
|
|
548
537
|
|
|
549
|
-
The
|
|
538
|
+
The index value accessor service is registered as a **singleton service**.
|
|
550
539
|
You must load the core module into the injection container if you want
|
|
551
540
|
to use it.
|
|
552
541
|
|
|
553
542
|
```ts
|
|
554
|
-
import {
|
|
555
|
-
InjectionContainer,
|
|
556
|
-
RsXCoreModule
|
|
557
|
-
} from '@rs-x/core';
|
|
543
|
+
import { InjectionContainer, RsXCoreModule } from '@rs-x/core';
|
|
558
544
|
|
|
559
545
|
InjectionContainer.load(RsXCoreModule);
|
|
560
546
|
```
|
|
@@ -563,35 +549,34 @@ There are two ways to get an instance:
|
|
|
563
549
|
|
|
564
550
|
1. Using the injection container
|
|
565
551
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
const indexValueAccessor: IIndexValueAccessor = InjectionContainer.get(
|
|
574
|
-
RsXCoreInjectionTokens.IIndexValueAccessor
|
|
575
|
-
);
|
|
576
|
-
```
|
|
552
|
+
```ts
|
|
553
|
+
import {
|
|
554
|
+
IIndexValueAccessor,
|
|
555
|
+
InjectionContainer,
|
|
556
|
+
RsXCoreInjectionTokens,
|
|
557
|
+
} from '@rs-x/core';
|
|
577
558
|
|
|
578
|
-
|
|
559
|
+
const indexValueAccessor: IIndexValueAccessor = InjectionContainer.get(
|
|
560
|
+
RsXCoreInjectionTokens.IIndexValueAccessor,
|
|
561
|
+
);
|
|
562
|
+
```
|
|
579
563
|
|
|
580
|
-
|
|
581
|
-
import {
|
|
582
|
-
IIndexValueAccessor,
|
|
583
|
-
Inject,
|
|
584
|
-
RsXCoreInjectionTokens
|
|
585
|
-
} from '@rs-x/core';
|
|
586
|
-
|
|
587
|
-
export class MyClass {
|
|
564
|
+
2. Using the `@Inject` decorator
|
|
588
565
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
566
|
+
```ts
|
|
567
|
+
import {
|
|
568
|
+
IIndexValueAccessor,
|
|
569
|
+
Inject,
|
|
570
|
+
RsXCoreInjectionTokens,
|
|
571
|
+
} from '@rs-x/core';
|
|
572
|
+
|
|
573
|
+
export class MyClass {
|
|
574
|
+
constructor(
|
|
575
|
+
@Inject(RsXCoreInjectionTokens.IIndexValueAccessor)
|
|
576
|
+
private readonly _indexValueAccessor: IIndexValueAccessor,
|
|
577
|
+
) {}
|
|
578
|
+
}
|
|
579
|
+
```
|
|
595
580
|
|
|
596
581
|
### Customize the supported index value accessor list
|
|
597
582
|
|
|
@@ -657,257 +642,261 @@ For example, suppose we have a service that patches a property on an object so i
|
|
|
657
642
|
|
|
658
643
|
```ts
|
|
659
644
|
import {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
645
|
+
IDisposable,
|
|
646
|
+
IDisposableOwner,
|
|
647
|
+
InvalidOperationException,
|
|
648
|
+
IPropertyChange,
|
|
649
|
+
IPropertyDescriptor,
|
|
650
|
+
PropertyDescriptorType,
|
|
651
|
+
SingletonFactory,
|
|
652
|
+
Type,
|
|
653
|
+
UnsupportedException,
|
|
669
654
|
} from '@rs-x/core';
|
|
670
655
|
import { Observable, Subject } from 'rxjs';
|
|
671
656
|
|
|
672
657
|
interface IObserver extends IDisposable {
|
|
673
|
-
|
|
658
|
+
changed: Observable<IPropertyChange>;
|
|
674
659
|
}
|
|
675
660
|
|
|
676
661
|
class PropertObserver implements IObserver {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
662
|
+
private _isDisposed = false;
|
|
663
|
+
private _value: unknown;
|
|
664
|
+
private _propertyDescriptorWithTarget: IPropertyDescriptor;
|
|
665
|
+
private readonly _changed = new Subject<IPropertyChange>();
|
|
666
|
+
|
|
667
|
+
constructor(
|
|
668
|
+
private readonly _owner: IDisposableOwner,
|
|
669
|
+
private readonly _target: object,
|
|
670
|
+
private readonly _propertyName: string,
|
|
671
|
+
) {
|
|
672
|
+
this.patch();
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
public get changed(): Observable<IPropertyChange> {
|
|
676
|
+
return this._changed;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
public dispose(): void {
|
|
680
|
+
if (this._isDisposed) {
|
|
681
|
+
return;
|
|
692
682
|
}
|
|
693
683
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
if (!this._owner?.canDispose || this._owner.canDispose()) {
|
|
700
|
-
const propertyName = this._propertyName as string;
|
|
701
|
-
const value = this._target[propertyName];
|
|
702
|
-
//to prevent errors if is was non configurable
|
|
703
|
-
delete this._target[propertyName];
|
|
704
|
-
|
|
705
|
-
if (
|
|
706
|
-
this._propertyDescriptorWithTarget.type !==
|
|
707
|
-
PropertyDescriptorType.Function
|
|
708
|
-
) {
|
|
709
|
-
this._target[propertyName] = value
|
|
710
|
-
}
|
|
684
|
+
if (!this._owner?.canDispose || this._owner.canDispose()) {
|
|
685
|
+
const propertyName = this._propertyName as string;
|
|
686
|
+
const value = this._target[propertyName];
|
|
687
|
+
//to prevent errors if is was non configurable
|
|
688
|
+
delete this._target[propertyName];
|
|
711
689
|
|
|
712
|
-
|
|
713
|
-
|
|
690
|
+
if (
|
|
691
|
+
this._propertyDescriptorWithTarget.type !==
|
|
692
|
+
PropertyDescriptorType.Function
|
|
693
|
+
) {
|
|
694
|
+
this._target[propertyName] = value;
|
|
695
|
+
}
|
|
714
696
|
|
|
715
|
-
|
|
697
|
+
this._propertyDescriptorWithTarget = undefined;
|
|
716
698
|
}
|
|
717
699
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
this._target,
|
|
721
|
-
this._propertyName
|
|
722
|
-
);
|
|
723
|
-
const descriptor = descriptorWithTarget.descriptor;
|
|
724
|
-
let newDescriptor: PropertyDescriptor;
|
|
725
|
-
|
|
726
|
-
if (descriptorWithTarget.type === PropertyDescriptorType.Function) {
|
|
727
|
-
throw new UnsupportedException('Methods are not supported')
|
|
728
|
-
} else if (!descriptor.get && !descriptor.set) {
|
|
729
|
-
newDescriptor =
|
|
730
|
-
this.createFieldPropertyDescriptor(descriptorWithTarget);
|
|
731
|
-
} else if (descriptor.set) {
|
|
732
|
-
newDescriptor =
|
|
733
|
-
this.createWritablePropertyDescriptor(descriptorWithTarget);
|
|
734
|
-
} else {
|
|
735
|
-
throw new InvalidOperationException(
|
|
736
|
-
`Property '${this._propertyName}' can not be watched because it is readonly`
|
|
737
|
-
);
|
|
738
|
-
}
|
|
700
|
+
this._owner?.release?.();
|
|
701
|
+
}
|
|
739
702
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
703
|
+
private patch(): void {
|
|
704
|
+
const descriptorWithTarget = Type.getPropertyDescriptor(
|
|
705
|
+
this._target,
|
|
706
|
+
this._propertyName,
|
|
707
|
+
);
|
|
708
|
+
const descriptor = descriptorWithTarget.descriptor;
|
|
709
|
+
let newDescriptor: PropertyDescriptor;
|
|
710
|
+
|
|
711
|
+
if (descriptorWithTarget.type === PropertyDescriptorType.Function) {
|
|
712
|
+
throw new UnsupportedException('Methods are not supported');
|
|
713
|
+
} else if (!descriptor.get && !descriptor.set) {
|
|
714
|
+
newDescriptor = this.createFieldPropertyDescriptor(descriptorWithTarget);
|
|
715
|
+
} else if (descriptor.set) {
|
|
716
|
+
newDescriptor =
|
|
717
|
+
this.createWritablePropertyDescriptor(descriptorWithTarget);
|
|
718
|
+
} else {
|
|
719
|
+
throw new InvalidOperationException(
|
|
720
|
+
`Property '${this._propertyName}' can not be watched because it is readonly`,
|
|
721
|
+
);
|
|
743
722
|
}
|
|
744
723
|
|
|
745
|
-
|
|
746
|
-
this._value = change.newValue;
|
|
724
|
+
Object.defineProperty(this._target, this._propertyName, newDescriptor);
|
|
747
725
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
...change,
|
|
751
|
-
chain: [{ object: this._target, id: this._propertyName }],
|
|
752
|
-
target: this._target,
|
|
753
|
-
id,
|
|
754
|
-
});
|
|
755
|
-
}
|
|
726
|
+
this._propertyDescriptorWithTarget = descriptorWithTarget;
|
|
727
|
+
}
|
|
756
728
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
): PropertyDescriptor {
|
|
760
|
-
const newDescriptor = { ...descriptorWithTarget.descriptor };
|
|
729
|
+
private emitChange(change: Partial<IPropertyChange>, id: unknown) {
|
|
730
|
+
this._value = change.newValue;
|
|
761
731
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
732
|
+
this._changed.next({
|
|
733
|
+
arguments: [],
|
|
734
|
+
...change,
|
|
735
|
+
chain: [{ object: this._target, id: this._propertyName }],
|
|
736
|
+
target: this._target,
|
|
737
|
+
id,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
765
740
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
};
|
|
741
|
+
private createFieldPropertyDescriptor(
|
|
742
|
+
descriptorWithTarget: IPropertyDescriptor,
|
|
743
|
+
): PropertyDescriptor {
|
|
744
|
+
const newDescriptor = { ...descriptorWithTarget.descriptor };
|
|
771
745
|
|
|
772
|
-
|
|
746
|
+
newDescriptor.get = () => this._value;
|
|
747
|
+
delete newDescriptor.writable;
|
|
748
|
+
delete newDescriptor.value;
|
|
773
749
|
|
|
774
|
-
|
|
775
|
-
|
|
750
|
+
newDescriptor.set = (value) => {
|
|
751
|
+
if (value !== this._value) {
|
|
752
|
+
this.emitChange({ newValue: value }, this._propertyName);
|
|
753
|
+
}
|
|
754
|
+
};
|
|
776
755
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
756
|
+
this._value = this._target[this._propertyName];
|
|
757
|
+
|
|
758
|
+
return newDescriptor;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
private createWritablePropertyDescriptor(
|
|
762
|
+
descriptorWithTarget: IPropertyDescriptor,
|
|
763
|
+
): PropertyDescriptor {
|
|
764
|
+
const newDescriptor = { ...descriptorWithTarget.descriptor };
|
|
765
|
+
const oldSetter = descriptorWithTarget.descriptor.set;
|
|
766
|
+
newDescriptor.set = (value) => {
|
|
767
|
+
const oldValue = this._target[this._propertyName];
|
|
768
|
+
if (value !== oldValue) {
|
|
769
|
+
oldSetter.call(this._target, value);
|
|
770
|
+
this.emitChange({ newValue: value }, this._propertyName);
|
|
771
|
+
}
|
|
772
|
+
};
|
|
789
773
|
|
|
790
|
-
|
|
774
|
+
this._value = this._target[this._propertyName];
|
|
791
775
|
|
|
792
|
-
|
|
793
|
-
|
|
776
|
+
return newDescriptor;
|
|
777
|
+
}
|
|
794
778
|
}
|
|
795
779
|
|
|
796
|
-
class PropertyObserverManager
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
)
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
780
|
+
class PropertyObserverManager extends SingletonFactory<
|
|
781
|
+
string,
|
|
782
|
+
string,
|
|
783
|
+
IObserver,
|
|
784
|
+
string
|
|
785
|
+
> {
|
|
786
|
+
constructor(
|
|
787
|
+
private readonly _object: object,
|
|
788
|
+
private readonly releaseObject: () => void,
|
|
789
|
+
) {
|
|
790
|
+
super();
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
public override getId(propertyName: string): string {
|
|
794
|
+
return propertyName;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
protected override createId(propertyName: string): string {
|
|
798
|
+
return propertyName;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
protected override createInstance(
|
|
802
|
+
propertyName: string,
|
|
803
|
+
id: string,
|
|
804
|
+
): IObserver {
|
|
805
|
+
return new PropertObserver(
|
|
806
|
+
{
|
|
807
|
+
canDispose: () => this.getReferenceCount(id) === 1,
|
|
808
|
+
release: () => this.release(id),
|
|
809
|
+
},
|
|
810
|
+
this._object,
|
|
811
|
+
propertyName,
|
|
812
|
+
);
|
|
813
|
+
}
|
|
817
814
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
): IObserver {
|
|
822
|
-
return new PropertObserver(
|
|
823
|
-
{
|
|
824
|
-
canDispose: () => this.getReferenceCount(id) === 1,
|
|
825
|
-
release: () => this.release(id),
|
|
826
|
-
},
|
|
827
|
-
this._object,
|
|
828
|
-
propertyName
|
|
829
|
-
);
|
|
830
|
-
}
|
|
815
|
+
protected override onReleased(): void {
|
|
816
|
+
this.releaseObject();
|
|
817
|
+
}
|
|
831
818
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
protected override releaseInstance(observer: IObserver): void {
|
|
837
|
-
observer.dispose();
|
|
838
|
-
}
|
|
819
|
+
protected override releaseInstance(observer: IObserver): void {
|
|
820
|
+
observer.dispose();
|
|
821
|
+
}
|
|
839
822
|
}
|
|
840
823
|
|
|
841
|
-
class ObjectPropertyObserverManager
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
824
|
+
class ObjectPropertyObserverManager extends SingletonFactory<
|
|
825
|
+
object,
|
|
826
|
+
object,
|
|
827
|
+
PropertyObserverManager
|
|
828
|
+
> {
|
|
829
|
+
constructor() {
|
|
830
|
+
super();
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
public override getId(context: object): object {
|
|
834
|
+
return context;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
protected override createId(context: object): object {
|
|
838
|
+
return context;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
protected override createInstance(context: object): PropertyObserverManager {
|
|
842
|
+
return new PropertyObserverManager(context, () => this.release(context));
|
|
843
|
+
}
|
|
844
|
+
protected override releaseInstance(
|
|
845
|
+
propertyObserverManager: PropertyObserverManager,
|
|
846
|
+
): void {
|
|
847
|
+
propertyObserverManager.dispose();
|
|
848
|
+
}
|
|
864
849
|
}
|
|
865
850
|
|
|
866
851
|
class PropertyObserverFactory {
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
852
|
+
private readonly _objectPropertyObserverManager =
|
|
853
|
+
new ObjectPropertyObserverManager();
|
|
854
|
+
|
|
855
|
+
public create(context: object, propertyName: string): IObserver {
|
|
856
|
+
return this._objectPropertyObserverManager
|
|
857
|
+
.create(context)
|
|
858
|
+
.instance.create(propertyName).instance;
|
|
859
|
+
}
|
|
874
860
|
}
|
|
875
861
|
|
|
876
862
|
export const run = (() => {
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
863
|
+
const context = {
|
|
864
|
+
a: 10,
|
|
865
|
+
};
|
|
866
|
+
const propertyObserverFactory = new PropertyObserverFactory();
|
|
867
|
+
|
|
868
|
+
const aObserver1 = propertyObserverFactory.create(context, 'a');
|
|
869
|
+
const aObserver2 = propertyObserverFactory.create(context, 'a');
|
|
870
|
+
|
|
871
|
+
const changeSubsription1 = aObserver1.changed.subscribe((change) => {
|
|
872
|
+
console.log('Observer 1:');
|
|
873
|
+
console.log(change.newValue);
|
|
874
|
+
});
|
|
875
|
+
const changeSubsription2 = aObserver1.changed.subscribe((change) => {
|
|
876
|
+
console.log('Observer 2:');
|
|
877
|
+
console.log(change.newValue);
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
console.log(
|
|
881
|
+
'You can observe the same property multiple times but only one observer will be create:',
|
|
882
|
+
);
|
|
883
|
+
console.log(aObserver1 === aObserver2);
|
|
884
|
+
|
|
885
|
+
console.log('Changing value to 20:');
|
|
886
|
+
|
|
887
|
+
context.a = 20;
|
|
888
|
+
|
|
889
|
+
// Dispose of the observers
|
|
890
|
+
aObserver1.dispose();
|
|
891
|
+
aObserver2.dispose();
|
|
892
|
+
// Unsubsribe to the changed event
|
|
893
|
+
changeSubsription1.unsubscribe();
|
|
894
|
+
changeSubsription2.unsubscribe();
|
|
907
895
|
})();
|
|
908
896
|
```
|
|
909
897
|
|
|
910
898
|
**Output:**
|
|
899
|
+
|
|
911
900
|
```console
|
|
912
901
|
Running demo: demo/src/rs-x-core/implementation-of-singleton-factory.ts
|
|
913
902
|
You can observe the same property multiple times but only one observer will be create:
|
|
@@ -921,61 +910,60 @@ Observer 2:
|
|
|
921
910
|
|
|
922
911
|
In this example, we have derived two classes from `SingletonFactory`:
|
|
923
912
|
|
|
924
|
-
|
|
925
|
-
|
|
913
|
+
- **`PropertyObserverManager`** – ensures that only one `PropertyObserver` is created per property.
|
|
914
|
+
- **`ObjectPropertyObserverManager`** – ensures that only one `PropertyObserverManager` is created per object.
|
|
926
915
|
|
|
927
|
-
It is good practice **not to expose classes derived from `SingletonFactory`** directly, but to use them internally to keep the interface simple.
|
|
916
|
+
It is good practice **not to expose classes derived from `SingletonFactory`** directly, but to use them internally to keep the interface simple.
|
|
928
917
|
|
|
929
|
-
For example, we have created a class **`PropertyObserverFactory`** that internally uses `ObjectPropertyObserverManager`.
|
|
918
|
+
For example, we have created a class **`PropertyObserverFactory`** that internally uses `ObjectPropertyObserverManager`.
|
|
930
919
|
|
|
931
920
|
The `PropertyObserver` class implements a `dispose` method, which ensures that it is released when there are no references left.
|
|
932
921
|
|
|
933
|
-
|
|
934
922
|
## Error Log
|
|
935
923
|
|
|
936
924
|
Basic logging using `console.error`
|
|
937
925
|
|
|
938
926
|
### interface IErrorLog
|
|
939
927
|
|
|
940
|
-
|
|
941
928
|
```ts
|
|
942
929
|
export interface IErrorLog {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
930
|
+
readonly error: Observable<IError>;
|
|
931
|
+
add(error: IError): void;
|
|
932
|
+
clear(): void;
|
|
946
933
|
}
|
|
947
934
|
```
|
|
948
935
|
|
|
949
936
|
### Members
|
|
950
937
|
|
|
951
938
|
### **error**
|
|
939
|
+
|
|
952
940
|
**Type:** `Observable<IError>`
|
|
953
941
|
event emitted when error is added
|
|
954
942
|
|
|
955
943
|
---
|
|
956
944
|
|
|
957
945
|
#### **add(error)**
|
|
946
|
+
|
|
958
947
|
log a new error and emit error event.
|
|
959
948
|
|
|
960
949
|
| Parameter | Type | Description |
|
|
961
950
|
| --------- | -------- | ----------- |
|
|
962
951
|
| **error** | `IError` | error. |
|
|
963
952
|
|
|
964
|
-
|
|
965
|
-
**Returns:** `void`
|
|
953
|
+
**Returns:** `void`
|
|
966
954
|
|
|
967
955
|
---
|
|
968
956
|
|
|
969
957
|
#### **clear()**
|
|
958
|
+
|
|
970
959
|
removes all logged errors
|
|
971
960
|
|
|
972
|
-
**Returns:** `void`
|
|
961
|
+
**Returns:** `void`
|
|
973
962
|
|
|
974
963
|
---
|
|
975
964
|
|
|
976
965
|
The default implementation uses `console.error` to log an error.
|
|
977
966
|
|
|
978
|
-
|
|
979
967
|
### Get an instance of the Error Log
|
|
980
968
|
|
|
981
969
|
The error log is registered as a **singleton service**.
|
|
@@ -983,10 +971,7 @@ You must load the core module into the injection container if you want
|
|
|
983
971
|
to use it.
|
|
984
972
|
|
|
985
973
|
```ts
|
|
986
|
-
import {
|
|
987
|
-
InjectionContainer,
|
|
988
|
-
RsXCoreModule
|
|
989
|
-
} from '@rs-x/core';
|
|
974
|
+
import { InjectionContainer, RsXCoreModule } from '@rs-x/core';
|
|
990
975
|
|
|
991
976
|
InjectionContainer.load(RsXCoreModule);
|
|
992
977
|
```
|
|
@@ -995,75 +980,73 @@ There are two ways to get an instance:
|
|
|
995
980
|
|
|
996
981
|
1. Using the injection container
|
|
997
982
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
983
|
+
```ts
|
|
984
|
+
import {
|
|
985
|
+
IErrorLog,
|
|
986
|
+
InjectionContainer,
|
|
987
|
+
RsXCoreInjectionTokens,
|
|
988
|
+
} from '@rs-x/core';
|
|
989
|
+
|
|
990
|
+
const errorLog: IErrorLog = InjectionContainer.get(
|
|
991
|
+
RsXCoreInjectionTokens.IErrorLog,
|
|
992
|
+
);
|
|
993
|
+
```
|
|
1009
994
|
|
|
1010
995
|
2. Using the `@Inject` decorator
|
|
1011
996
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
IErrorLog,
|
|
1015
|
-
Inject,
|
|
1016
|
-
RsXCoreInjectionTokens
|
|
1017
|
-
} from '@rs-x/core';
|
|
1018
|
-
|
|
1019
|
-
export class MyClass {
|
|
997
|
+
```ts
|
|
998
|
+
import { IErrorLog, Inject, RsXCoreInjectionTokens } from '@rs-x/core';
|
|
1020
999
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1000
|
+
export class MyClass {
|
|
1001
|
+
constructor(
|
|
1002
|
+
@Inject(RsXCoreInjectionTokens.IErrorLog)
|
|
1003
|
+
private readonly _errorLog: IErrorLog,
|
|
1004
|
+
) {}
|
|
1005
|
+
}
|
|
1006
|
+
```
|
|
1027
1007
|
|
|
1028
1008
|
The following example shows how to use the error log
|
|
1029
1009
|
|
|
1030
1010
|
```ts
|
|
1031
1011
|
import {
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1012
|
+
IErrorLog,
|
|
1013
|
+
InjectionContainer,
|
|
1014
|
+
printValue,
|
|
1015
|
+
RsXCoreInjectionTokens,
|
|
1016
|
+
RsXCoreModule,
|
|
1037
1017
|
} from '@rs-x/core';
|
|
1038
1018
|
|
|
1039
1019
|
// Load the core module into the injection container
|
|
1040
1020
|
InjectionContainer.load(RsXCoreModule);
|
|
1041
|
-
const errorLog: IErrorLog = InjectionContainer.get(
|
|
1021
|
+
const errorLog: IErrorLog = InjectionContainer.get(
|
|
1022
|
+
RsXCoreInjectionTokens.IErrorLog,
|
|
1023
|
+
);
|
|
1042
1024
|
|
|
1043
1025
|
export const run = (() => {
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1026
|
+
const context = {
|
|
1027
|
+
name: 'My error context',
|
|
1028
|
+
};
|
|
1029
|
+
const changeSubscription = errorLog.error.subscribe((e) => {
|
|
1030
|
+
console.log('Emmitted error');
|
|
1031
|
+
printValue(e);
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
try {
|
|
1035
|
+
throw new Error('Oops an error');
|
|
1036
|
+
} catch (e) {
|
|
1037
|
+
errorLog.add({
|
|
1038
|
+
exception: e,
|
|
1039
|
+
message: 'Oops',
|
|
1040
|
+
context,
|
|
1050
1041
|
});
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
} catch (e) {
|
|
1055
|
-
errorLog.add({
|
|
1056
|
-
exception: e,
|
|
1057
|
-
message: 'Oops',
|
|
1058
|
-
context,
|
|
1059
|
-
});
|
|
1060
|
-
} finally {
|
|
1061
|
-
changeSubscription.unsubscribe();
|
|
1062
|
-
}
|
|
1042
|
+
} finally {
|
|
1043
|
+
changeSubscription.unsubscribe();
|
|
1044
|
+
}
|
|
1063
1045
|
})();
|
|
1064
1046
|
```
|
|
1065
1047
|
|
|
1066
1048
|
**Output:**
|
|
1049
|
+
|
|
1067
1050
|
```console
|
|
1068
1051
|
Running demo: demo/src/rs-x-core/error-log.ts
|
|
1069
1052
|
Emmitted error
|
|
@@ -1090,7 +1073,7 @@ It is particularly useful in **tests**, **async workflows**, and **event-driven
|
|
|
1090
1073
|
- Waits for one or multiple observable emissions
|
|
1091
1074
|
- Supports synchronous, `Promise`, or `Observable` triggers
|
|
1092
1075
|
- Optional timeout handling
|
|
1093
|
-
- Ability to ignore the initial observable value. For example when the event is implemented with
|
|
1076
|
+
- Ability to ignore the initial observable value. For example when the event is implemented with `BehaviorSubject` or `ReplaySubject`
|
|
1094
1077
|
|
|
1095
1078
|
---
|
|
1096
1079
|
|
|
@@ -1110,7 +1093,6 @@ constructor(
|
|
|
1110
1093
|
| eventName | E | Name of the observable property to wait on |
|
|
1111
1094
|
| options | WaitOptions<T, E, R> | Optional configuration |
|
|
1112
1095
|
|
|
1113
|
-
|
|
1114
1096
|
#### `WaitOptions<T, E, R>`
|
|
1115
1097
|
|
|
1116
1098
|
Configuration options for waiting.
|
|
@@ -1121,9 +1103,8 @@ Configuration options for waiting.
|
|
|
1121
1103
|
| timeout | `number` | 100 | Timeout in milliseconds |
|
|
1122
1104
|
| ignoreInitialValue | `boolean` | false | Ignore the first emitted value |
|
|
1123
1105
|
|
|
1124
|
-
---
|
|
1106
|
+
---
|
|
1125
1107
|
|
|
1126
|
-
|
|
1127
1108
|
### Methods
|
|
1128
1109
|
|
|
1129
1110
|
#### **wait(trigger)**
|
|
@@ -1138,42 +1119,39 @@ Waits for the observable to emit the specified number of events after running th
|
|
|
1138
1119
|
|
|
1139
1120
|
---
|
|
1140
1121
|
|
|
1141
|
-
|
|
1142
1122
|
### Example
|
|
1143
1123
|
|
|
1144
1124
|
```ts
|
|
1145
|
-
import {
|
|
1146
|
-
printValue,
|
|
1147
|
-
WaitForEvent,
|
|
1148
|
-
|
|
1149
|
-
} from '@rs-x/core';
|
|
1125
|
+
import { printValue, WaitForEvent } from '@rs-x/core';
|
|
1150
1126
|
import { Observable, Subject } from 'rxjs';
|
|
1151
1127
|
|
|
1152
1128
|
export const run = (async () => {
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1129
|
+
class MyEventContext {
|
|
1130
|
+
private readonly _message = new Subject<string>();
|
|
1156
1131
|
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
public emitMessage(message: string): void {
|
|
1162
|
-
this._message.next(message);
|
|
1163
|
-
}
|
|
1132
|
+
public get message(): Observable<string> {
|
|
1133
|
+
return this._message;
|
|
1164
1134
|
}
|
|
1165
1135
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1136
|
+
public emitMessage(message: string): void {
|
|
1137
|
+
this._message.next(message);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
const eventContext = new MyEventContext();
|
|
1142
|
+
const result = await new WaitForEvent(eventContext, 'message', {
|
|
1143
|
+
count: 2,
|
|
1144
|
+
}).wait(() => {
|
|
1145
|
+
eventContext.emitMessage('Hello');
|
|
1146
|
+
eventContext.emitMessage('hi');
|
|
1147
|
+
});
|
|
1148
|
+
console.log('Emitted events:');
|
|
1149
|
+
printValue(result);
|
|
1173
1150
|
})();
|
|
1174
1151
|
```
|
|
1175
1152
|
|
|
1176
1153
|
**Output:**
|
|
1154
|
+
|
|
1177
1155
|
```console
|
|
1178
1156
|
Running demo: demo/src/rs-x-core/wait-for-event.ts
|
|
1179
1157
|
Emitted events:
|
|
@@ -1181,4 +1159,4 @@ Emitted events:
|
|
|
1181
1159
|
Hello,
|
|
1182
1160
|
hi
|
|
1183
1161
|
]
|
|
1184
|
-
```
|
|
1162
|
+
```
|