ngxs-synchronizers 1.0.0 → 2.0.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.
Files changed (40) hide show
  1. package/LICENSE +18 -18
  2. package/README.md +168 -168
  3. package/docs/api-reference.md +700 -700
  4. package/docs/usage-guide.md +385 -385
  5. package/{esm2015/decorators/sync-class.js → esm2020/decorators/sync-class.mjs} +1 -1
  6. package/esm2020/decorators/sync-state.mjs +60 -0
  7. package/{esm2015/index.js → esm2020/index.mjs} +1 -1
  8. package/esm2020/module.mjs +19 -0
  9. package/esm2020/ngxs-synchronizers.mjs +5 -0
  10. package/esm2020/state-selector.mjs +221 -0
  11. package/esm2020/sync-store.mjs +32 -0
  12. package/esm2020/synchronizer/collection-synchronizer.mjs +2 -0
  13. package/esm2020/synchronizer/property-synchronizer.mjs +2 -0
  14. package/esm2020/synchronizer/state-synchronizer.mjs +36 -0
  15. package/esm2020/synchronizer/synchronizer-dictionary.mjs +26 -0
  16. package/esm2020/synchronizer/synchronizer.mjs +2 -0
  17. package/fesm2015/{ngxs-synchronizers.js → ngxs-synchronizers.mjs} +38 -26
  18. package/fesm2015/ngxs-synchronizers.mjs.map +1 -0
  19. package/fesm2020/ngxs-synchronizers.mjs +427 -0
  20. package/fesm2020/ngxs-synchronizers.mjs.map +1 -0
  21. package/module.d.ts +4 -0
  22. package/ngxs-synchronizers.d.ts +1 -1
  23. package/package.json +22 -9
  24. package/state-selector.d.ts +2 -2
  25. package/sync-store.d.ts +3 -0
  26. package/synchronizer/synchronizer-dictionary.d.ts +1 -1
  27. package/bundles/ngxs-synchronizers.umd.js +0 -791
  28. package/bundles/ngxs-synchronizers.umd.js.map +0 -1
  29. package/esm2015/decorators/sync-state.js +0 -54
  30. package/esm2015/module.js +0 -12
  31. package/esm2015/ngxs-synchronizers.js +0 -6
  32. package/esm2015/state-selector.js +0 -221
  33. package/esm2015/sync-store.js +0 -32
  34. package/esm2015/synchronizer/collection-synchronizer.js +0 -2
  35. package/esm2015/synchronizer/property-synchronizer.js +0 -2
  36. package/esm2015/synchronizer/state-synchronizer.js +0 -33
  37. package/esm2015/synchronizer/synchronizer-dictionary.js +0 -27
  38. package/esm2015/synchronizer/synchronizer.js +0 -2
  39. package/fesm2015/ngxs-synchronizers.js.map +0 -1
  40. package/ngxs-synchronizers.metadata.json +0 -1
@@ -1,386 +1,386 @@
1
- # NGXS Synchronizers Usage Guide
2
-
3
- A full [**API reference**](/docs/api-reference.md) is also available.
4
-
5
- * [**Installation**](#installation)
6
- * **SyncState**
7
- * [**Defining a state**](#defining-a-state)
8
- * [**SyncStore**](#syncstore)
9
- * [**StateSelector**](#stateselector)
10
- * **Property Synchronizers**
11
- * [**Defining a property synchronizer**](#defining-a-property-synchronizer)
12
- * [**Using a property synchronizer**](#using-a-property-synchronizer)
13
- * **Collection Synchronizers**
14
- * [**Defining a collection synchronizer**](#defining-a-collection-synchronizer)
15
- * [**Using a collection synchronizer**](#using-a-collection-synchronizer)
16
- * **State Synchronizers**
17
- * [**Defining a state synchronizer**](#defining-a-state-synchronizer)
18
- * [**Using a state synchronizer**](#using-a-state-synchronizer)
19
-
20
- ## What is ngxs-synchronizers?
21
-
22
- **ngxs-synchronizers** is an extension to NGXS that allows for easy synchronization between a local NGXS state and an external data source (i.e. backend service, database, JSON file, etc.) through special Angular services called _Synchronizers_. Synchronizers can be used to read and write data from an external data source, and can be used to more easily manage application state synchronization and data dependency requirements.
23
-
24
- ## When should I use ngxs-synchronizers?
25
-
26
- ngxs-synchronizers is useful for applications that require synchronizing data between the local application and a remote data store like a backend web service or a database. If your application state relies on external data sources, ngxs-synchronizers can be useful for simplifying and abstracting communication and synchronization between your application and the remote data source.
27
-
28
- ## Installation
29
-
30
- ngxs-synchronizers requires **@ngxs/store** as a dependency. Both can be installed from npm with the following command:
31
-
32
- ```bash
33
- npm install @ngxs/store ngxs-synchronizers
34
- ```
35
-
36
- You must import the `NgxsSyncModule` module into the root of your application.
37
-
38
- Since ngxs-synchronizers is built on top of NGXS, it is recommended to read through the [NGXS documentation](https://www.ngxs.io/) to understand how NGXS works before proceeding.
39
-
40
- ## Defining a state
41
- _[NGXS docs](https://www.ngxs.io/concepts/state)_
42
-
43
- States are classes along with decorators to describe metadata and action mappings. Normally in NGXS we would use the `@State` decorator. ngxs-synchronizers provides its own `@SyncState` decorator that should be used instead. This decorator behaves like `@State` and adds extra configuration options specific to ngxs-synchronizers.
44
-
45
- Let's create our first state class:
46
-
47
- ```ts
48
- import { SyncState } from 'ngxs-synchronizers';
49
-
50
- interface Session {
51
- username: string;
52
- messages: string[];
53
- }
54
-
55
- @SyncState<Session>({
56
- name: 'session',
57
- defaults: null
58
- })
59
- @Injectable()
60
- class SessionState {
61
- ...
62
- }
63
- ```
64
-
65
- So far this is a straightforward state class definition that is not much different than a normal NGXS state definition. However, we must now define a synchronizer class that will allow us to read data from our remote data store.
66
-
67
- ## SyncStore
68
- _[NGXS docs](https://www.ngxs.io/concepts/store)_
69
-
70
- The `SyncStore` class is an extension of the `Store` class from NGXS. `SyncStore` has all the same methods and behavior as `Store`, but also adds a new `state` method that allows us to interact with our synchronizers.
71
-
72
- ## StateSelector
73
-
74
- `StateSelector` is the class used to manage synchronizers and synchronization state. It is primarily used to invoke synchronizers and monitor the status of in-flight synchronizations.
75
-
76
- ## Defining a property synchronizer
77
-
78
- In this example, we are going to write a _property_ synchronizer that gets the user's latest messages from the backend. Let's assume an existing Angular service called ```Messages``` that contains a ```get``` method for retreiving the list of messages for a given username from the backend.
79
-
80
- ### Property synchronizer example:
81
-
82
- ```ts
83
- import { PropertySynchronizer } from 'ngxs-synchronizers';
84
-
85
- @Injectable({ providedIn: 'root' })
86
- export class MessagesSynchronizer implements PropertySynchronizer<Session, 'messages'> {
87
-
88
- // We need to know the username in order to fetch the messages
89
- // This creates a dependency on the `username` field.
90
- public readonly requiredProperties = ['username'];
91
-
92
- // messagesProvider is an existing service for retrieving user messages from the backend
93
- constructor(private readonly messagesProvider: Messages) {}
94
-
95
- public read({ username }: Session): Observable<string[]> {
96
- // Get current messages for the active user
97
- return this.messagesProvider.get(username);
98
- }
99
- }
100
- ```
101
-
102
- Our newly created ```MessagesSynchronizer``` service implements the ```PropertySynchronizer``` interface and specifies that it's managing the ```messages``` property on our ```Session``` model. We are also specifying that this synchronizer relies on the latest value of the ```username``` field from our ```Session``` store. In this example, we are assuming this value already exists in our local state. However, if we also create a ```PropertySynchronizer``` for the ```username``` field, it will be automatically be invoked
103
- _before_ ```MessagesSynchronizer``` is invoked.
104
-
105
- The ```read``` method we have implemented receives all required fields from the object that we declared in `requiredProperties`. Since we have specified that we require the ```username``` field, the ```read``` method will receive a partial ```Session``` object that contains the current value for ```username```. The `read` method should return an ```Observable``` with a return type that corresponds to the type of the field we're synchronizing, which in this case is a ```string[]```.
106
-
107
- Now that we have created our property synchronizer, we need to register it with our ```SessionState``` class definition from earlier:
108
-
109
- ```ts
110
- @SyncState<Session>({
111
- name: 'session',
112
- defaults: null,
113
- synchronizers: {
114
- // Register the `MessagesSynchronizer` for the `messages` property
115
- messages: MessagesSynchronizer
116
- }
117
- })
118
- @Injectable()
119
- class SessionState {
120
- ...
121
- }
122
- ```
123
-
124
- With the `MessagesSynchronizer` registered, we can now use it to read data from the backend service.
125
-
126
- ## Using a property synchronizer
127
-
128
- In this example we will use `SyncStore` and `StateSelector` to invoke our `MessageSynchronizer`.
129
-
130
- Assume that we have a page in our application that shows the user's messages. When the user navigates to this page, we want to make sure we have fetched the user's messages so that we can display them. To do this, we can use the `read` function of `MessageSynchronizer` to update our local `session` store with the latest messages from the backend service.
131
-
132
- To invoke our synchronizer, we must use a `StateSelector`:
133
-
134
-
135
- ```ts
136
- @Component({...})
137
- export class MessagesPage {
138
-
139
- public messages: string[];
140
-
141
- constructor(store: SyncStore) {
142
- // Fetch the latest messages from the backend
143
- store.state<Session>(SessionState)
144
- .syncProperty('messages')
145
- .subscribe(messages => this.messages = messages);
146
- }
147
- }
148
- ```
149
-
150
- When we call `SyncStore.state` with `SessionState`, we get a `StateSelector` object that represents the current synchronization state of our state. Calling `StateSelector.syncProperty` invokes the `read` function of `MessagesSynchronizer` to get the latest messages from the backend.
151
-
152
- This works well, however we might want to only fetch the messages from the backend once when the user first navigates to the page, and then offer a refresh mechanism for loading new messages, or re-fetch them periodically. We can replace the call to `syncProperty` with `requireProperty`, which will only make a request to the backend if the data does not already exist in the local application store. No request will be made if data already exists for the given property in the local application store.
153
-
154
- ### ```requireProperty``` example:
155
-
156
- ```ts
157
- @Component({...})
158
- export class MessagesPage {
159
-
160
- public messages: string[];
161
-
162
- constructor(store: SyncStore) {
163
- // Fetch the latest messages from the backend
164
- store.state<Session>(SessionState)
165
- .requireProperty('messages')
166
- .subscribe(messages => this.messages = messages);
167
- }
168
- }
169
- ```
170
-
171
- Now the user's messages will only be fetched from the backend the first time the user navigates to ```MessagesPage```. On each subsequent visit to the page, the previously fetched messages will be returned.
172
-
173
- ## Defining a collection synchronizer
174
-
175
- Unlike a property synchronizer, a collection synchronizer is responsible for synchronizing a collection of data in a store.
176
-
177
- In the next example, let's assume the user has access to an inventory of items, each with a unique ID. We could represent that inventory with the following types:
178
-
179
- ```ts
180
- export interface InventoryItem {
181
- id: string;
182
- name: string;
183
- }
184
-
185
- // An `Inventory` is a collection of `InventoryItem` objects keyed by their ID
186
- export type Inventory = Record<string, InventoryItem>;
187
- ```
188
-
189
- We could just fetch the entire list of inventory items and put them in our local application store, but this would not be feasible if our inventory is large and contains thousands of items.
190
-
191
- Instead, we can use a collection synchronizer to fetch only the items the user needs access to on a case-by-case basis as they progress through the application.
192
-
193
- First, let's create a new state definition to represent the inventory:
194
-
195
- ```ts
196
- import { SyncState } from 'ngxs-synchronizers';
197
-
198
- export interface InventoryItem {
199
- id: string;
200
- name: string;
201
- }
202
-
203
- export type Inventory = Record<string, InventoryItem>;
204
-
205
- @SyncState<Inventory>({
206
- name: 'inventory',
207
- defaults: null
208
- })
209
- @Injectable()
210
- class InventoryState {
211
- ...
212
- }
213
- ```
214
-
215
- Now, let's create a collection synchronizer for our new `inventory` state. Assume we have an `ItemInventory` Angular service that can retrieve individual inventory items from our remote data source:
216
-
217
- ```ts
218
- import { CollectionSynchronizer } from 'ngxs-synchronizers';
219
-
220
- @Injectable({ providedIn: 'root' })
221
- export class InventorySynchronizer implements CollectionSynchronizer<Inventory> {
222
-
223
- // itemInventory is an existing service for retrieving inventory items from the backend
224
- constructor(private readonly itemInventory: ItemInventory) {}
225
-
226
- public read(_inventory: Inventory, options: CollectionSynchronizer.ReadOptions<Inventory>): Observable<InventoryItem> {
227
- // Get the inventory item by the specified ID:
228
- const itemId = options.propertyName;
229
- return this.itemInventory.getItem(itemId);
230
- }
231
- }
232
- ```
233
-
234
- When a specific `InventoryItem` is requested by ID, our `InventorySynchronizer` will look up the specified item from the backend. Now we need to register our collection synchronizer with our `InventoryState` from earlier:
235
-
236
- ```ts
237
- @SyncState<Inventory>({
238
- name: 'inventory',
239
- defaults: null,
240
- // The `InventorySynchronizer` is used to synchronize all of the fields in this state
241
- synchronizers: InventorySynchronizer
242
- })
243
- @Injectable()
244
- class InventoryState {
245
- ...
246
- }
247
- ```
248
-
249
- `InventorySynchronizer` will now be used to handle any requests for inventory items.
250
-
251
- ## Using a collection synchronizer
252
-
253
- Using a collection synchronizer is very similar to using a property synchronizer, except that the properties being synced will be items of the collection that the synchronizer manages.
254
-
255
- Assume we have a page in our application that is used to show the details about a specific `InventoryItem`. We will need to look up the item from the backend when the user navigates to the page.
256
-
257
- To do this, we'll invoke our `InventorySynchronizer`:
258
-
259
- ```ts
260
- import { Route } from "@angular/router";
261
-
262
- @Component({...})
263
- export class InventoryItemPage {
264
-
265
- public item: InventoryItem;
266
-
267
- constructor(store: SyncStore, route: Route) {
268
- // Retrieve the `itemId` param from the page route
269
- route.params.pipe(
270
- map(params => params.itemId),
271
- switchMap((itemId: string) => {
272
- // Fetch the specified item from the backend using the `InventorySynchronizer`
273
- return store.state<Inventory>(InventoryState)
274
- .syncProperty(itemId);
275
- })
276
- ).subscribe(item => this.item = item);
277
- }
278
- }
279
- ```
280
-
281
- When we call `syncProperty` with the specified `itemId`, the `read` method from `InventorySynchronizer` will be invoked and retrieve the given item from the backend.
282
-
283
- As with property synchronizers, we could instead use `requireProperty` instead of `syncProperty` to only make a new request to the backend if the item data is not already in our local application store:
284
-
285
- ```ts
286
- store.state<Inventory>(InventoryState)
287
- .requireProperty(itemId);
288
- ```
289
-
290
- ## Defining a state synchronizer
291
-
292
- A state synchronizer is a special kind of property synchronizer that invokes all of the synchronizers defined for a given state. This allows you to call all of the synchronizers on a given state at once. This is useful for creating aggregate synchronizers for child states.
293
-
294
- Recall our `SessionState` definition from earlier:
295
-
296
- ```ts
297
- import { SyncState } from 'ngxs-synchronizers';
298
-
299
- interface Session {
300
- username: string;
301
- messages: string[];
302
- }
303
-
304
- @SyncState<Session>({
305
- name: 'session',
306
- defaults: null,
307
- synchronizers: {
308
- messages: MessagesSynchronizer
309
- }
310
- })
311
- @Injectable()
312
- class SessionState {
313
- ...
314
- }
315
- ```
316
-
317
- Now, we will create a new parent state called `ApplicationState`, which will have the `SessionState` as a child:
318
-
319
- ```ts
320
- import { SyncState } from 'ngxs-synchronizers';
321
-
322
- interface AppData {
323
- session: Session;
324
- }
325
-
326
- @SyncState<AppData>({
327
- name: 'app',
328
- defaults: null,
329
- children: [SessionState]
330
- })
331
- @Injectable()
332
- class AppState {
333
- ...
334
- }
335
- ```
336
-
337
- What if we wanted to synchronize our entire `session` state by invoking a synchronizer from the `AppState`? With state synchronizers, we can do just that.
338
-
339
- First, create a new synchronizer called `SessionSynchronizer`:
340
-
341
- ```ts
342
- import { Injector } from '@angular/core';
343
- import { StateSynchronizer } from 'ngxs-synchronizers';
344
-
345
- @Injectable({ providedIn: 'root' })
346
- export class SessionSynchronizer extends StateSynchronizer<AppData, "session"> {
347
-
348
- constructor(injector: Injector) {
349
- // Use the child `SessionState`
350
- super(injector, SessionState);
351
- }
352
- }
353
- ```
354
-
355
- `SessionSynchronizer` extends the `StateSynchronizer` class, which handles automatically invoking all child synchronizers of a given state definition. In this example, we are creating a state synchronizer for `SessionState`, which in turn will invoke the `MessageSynchronizer` for the `messages` field (and any other synchronizers defined in that state).
356
-
357
- This behavior can be customized by overriding the `read` method of the synchronizer and using the `StateSynchronizer.readSubset` method to invoke specific synchronizers.
358
-
359
- With our `SessionSynchronizer` defined, we need to register it with our `AppState` definition:
360
-
361
- ```ts
362
- @SyncState<AppData>({
363
- name: 'app',
364
- defaults: null,
365
- synchronizers: {
366
- // Register the `SessionSynchronizer`
367
- session: SessionSynchronizer
368
- }
369
- children: [SessionState]
370
- })
371
- @Injectable()
372
- class AppState {
373
- ...
374
- }
375
- ```
376
-
377
- Now when we synchronize on the `session` property in `AppState`, all of the synchronizers defined in `SessionState` will be invoked automatically.
378
-
379
- ## Using a state synchronizer
380
-
381
- Using a state synchronizer is identical to using a property synchronizer:
382
-
383
- ```ts
384
- store.state<AppData>(AppState)
385
- .syncProperty('session');
1
+ # NGXS Synchronizers Usage Guide
2
+
3
+ A full [**API reference**](/docs/api-reference.md) is also available.
4
+
5
+ * [**Installation**](#installation)
6
+ * **SyncState**
7
+ * [**Defining a state**](#defining-a-state)
8
+ * [**SyncStore**](#syncstore)
9
+ * [**StateSelector**](#stateselector)
10
+ * **Property Synchronizers**
11
+ * [**Defining a property synchronizer**](#defining-a-property-synchronizer)
12
+ * [**Using a property synchronizer**](#using-a-property-synchronizer)
13
+ * **Collection Synchronizers**
14
+ * [**Defining a collection synchronizer**](#defining-a-collection-synchronizer)
15
+ * [**Using a collection synchronizer**](#using-a-collection-synchronizer)
16
+ * **State Synchronizers**
17
+ * [**Defining a state synchronizer**](#defining-a-state-synchronizer)
18
+ * [**Using a state synchronizer**](#using-a-state-synchronizer)
19
+
20
+ ## What is ngxs-synchronizers?
21
+
22
+ **ngxs-synchronizers** is an extension to NGXS that allows for easy synchronization between a local NGXS state and an external data source (i.e. backend service, database, JSON file, etc.) through special Angular services called _Synchronizers_. Synchronizers can be used to read and write data from an external data source, and can be used to more easily manage application state synchronization and data dependency requirements.
23
+
24
+ ## When should I use ngxs-synchronizers?
25
+
26
+ ngxs-synchronizers is useful for applications that require synchronizing data between the local application and a remote data store like a backend web service or a database. If your application state relies on external data sources, ngxs-synchronizers can be useful for simplifying and abstracting communication and synchronization between your application and the remote data source.
27
+
28
+ ## Installation
29
+
30
+ ngxs-synchronizers requires **@ngxs/store** as a dependency. Both can be installed from npm with the following command:
31
+
32
+ ```bash
33
+ npm install @ngxs/store ngxs-synchronizers
34
+ ```
35
+
36
+ You must import the `NgxsSyncModule` module into the root of your application.
37
+
38
+ Since ngxs-synchronizers is built on top of NGXS, it is recommended to read through the [NGXS documentation](https://www.ngxs.io/) to understand how NGXS works before proceeding.
39
+
40
+ ## Defining a state
41
+ _[NGXS docs](https://www.ngxs.io/concepts/state)_
42
+
43
+ States are classes along with decorators to describe metadata and action mappings. Normally in NGXS we would use the `@State` decorator. ngxs-synchronizers provides its own `@SyncState` decorator that should be used instead. This decorator behaves like `@State` and adds extra configuration options specific to ngxs-synchronizers.
44
+
45
+ Let's create our first state class:
46
+
47
+ ```ts
48
+ import { SyncState } from 'ngxs-synchronizers';
49
+
50
+ interface Session {
51
+ username: string;
52
+ messages: string[];
53
+ }
54
+
55
+ @SyncState<Session>({
56
+ name: 'session',
57
+ defaults: null
58
+ })
59
+ @Injectable()
60
+ class SessionState {
61
+ ...
62
+ }
63
+ ```
64
+
65
+ So far this is a straightforward state class definition that is not much different than a normal NGXS state definition. However, we must now define a synchronizer class that will allow us to read data from our remote data store.
66
+
67
+ ## SyncStore
68
+ _[NGXS docs](https://www.ngxs.io/concepts/store)_
69
+
70
+ The `SyncStore` class is an extension of the `Store` class from NGXS. `SyncStore` has all the same methods and behavior as `Store`, but also adds a new `state` method that allows us to interact with our synchronizers.
71
+
72
+ ## StateSelector
73
+
74
+ `StateSelector` is the class used to manage synchronizers and synchronization state. It is primarily used to invoke synchronizers and monitor the status of in-flight synchronizations.
75
+
76
+ ## Defining a property synchronizer
77
+
78
+ In this example, we are going to write a _property_ synchronizer that gets the user's latest messages from the backend. Let's assume an existing Angular service called ```Messages``` that contains a ```get``` method for retreiving the list of messages for a given username from the backend.
79
+
80
+ ### Property synchronizer example:
81
+
82
+ ```ts
83
+ import { PropertySynchronizer } from 'ngxs-synchronizers';
84
+
85
+ @Injectable({ providedIn: 'root' })
86
+ export class MessagesSynchronizer implements PropertySynchronizer<Session, 'messages'> {
87
+
88
+ // We need to know the username in order to fetch the messages
89
+ // This creates a dependency on the `username` field.
90
+ public readonly requiredProperties = ['username'];
91
+
92
+ // messagesProvider is an existing service for retrieving user messages from the backend
93
+ constructor(private readonly messagesProvider: Messages) {}
94
+
95
+ public read({ username }: Session): Observable<string[]> {
96
+ // Get current messages for the active user
97
+ return this.messagesProvider.get(username);
98
+ }
99
+ }
100
+ ```
101
+
102
+ Our newly created ```MessagesSynchronizer``` service implements the ```PropertySynchronizer``` interface and specifies that it's managing the ```messages``` property on our ```Session``` model. We are also specifying that this synchronizer relies on the latest value of the ```username``` field from our ```Session``` store. In this example, we are assuming this value already exists in our local state. However, if we also create a ```PropertySynchronizer``` for the ```username``` field, it will be automatically be invoked
103
+ _before_ ```MessagesSynchronizer``` is invoked.
104
+
105
+ The ```read``` method we have implemented receives all required fields from the object that we declared in `requiredProperties`. Since we have specified that we require the ```username``` field, the ```read``` method will receive a partial ```Session``` object that contains the current value for ```username```. The `read` method should return an ```Observable``` with a return type that corresponds to the type of the field we're synchronizing, which in this case is a ```string[]```.
106
+
107
+ Now that we have created our property synchronizer, we need to register it with our ```SessionState``` class definition from earlier:
108
+
109
+ ```ts
110
+ @SyncState<Session>({
111
+ name: 'session',
112
+ defaults: null,
113
+ synchronizers: {
114
+ // Register the `MessagesSynchronizer` for the `messages` property
115
+ messages: MessagesSynchronizer
116
+ }
117
+ })
118
+ @Injectable()
119
+ class SessionState {
120
+ ...
121
+ }
122
+ ```
123
+
124
+ With the `MessagesSynchronizer` registered, we can now use it to read data from the backend service.
125
+
126
+ ## Using a property synchronizer
127
+
128
+ In this example we will use `SyncStore` and `StateSelector` to invoke our `MessageSynchronizer`.
129
+
130
+ Assume that we have a page in our application that shows the user's messages. When the user navigates to this page, we want to make sure we have fetched the user's messages so that we can display them. To do this, we can use the `read` function of `MessageSynchronizer` to update our local `session` store with the latest messages from the backend service.
131
+
132
+ To invoke our synchronizer, we must use a `StateSelector`:
133
+
134
+
135
+ ```ts
136
+ @Component({...})
137
+ export class MessagesPage {
138
+
139
+ public messages: string[];
140
+
141
+ constructor(store: SyncStore) {
142
+ // Fetch the latest messages from the backend
143
+ store.state<Session>(SessionState)
144
+ .syncProperty('messages')
145
+ .subscribe(messages => this.messages = messages);
146
+ }
147
+ }
148
+ ```
149
+
150
+ When we call `SyncStore.state` with `SessionState`, we get a `StateSelector` object that represents the current synchronization state of our state. Calling `StateSelector.syncProperty` invokes the `read` function of `MessagesSynchronizer` to get the latest messages from the backend.
151
+
152
+ This works well, however we might want to only fetch the messages from the backend once when the user first navigates to the page, and then offer a refresh mechanism for loading new messages, or re-fetch them periodically. We can replace the call to `syncProperty` with `requireProperty`, which will only make a request to the backend if the data does not already exist in the local application store. No request will be made if data already exists for the given property in the local application store.
153
+
154
+ ### ```requireProperty``` example:
155
+
156
+ ```ts
157
+ @Component({...})
158
+ export class MessagesPage {
159
+
160
+ public messages: string[];
161
+
162
+ constructor(store: SyncStore) {
163
+ // Fetch the latest messages from the backend
164
+ store.state<Session>(SessionState)
165
+ .requireProperty('messages')
166
+ .subscribe(messages => this.messages = messages);
167
+ }
168
+ }
169
+ ```
170
+
171
+ Now the user's messages will only be fetched from the backend the first time the user navigates to ```MessagesPage```. On each subsequent visit to the page, the previously fetched messages will be returned.
172
+
173
+ ## Defining a collection synchronizer
174
+
175
+ Unlike a property synchronizer, a collection synchronizer is responsible for synchronizing a collection of data in a store.
176
+
177
+ In the next example, let's assume the user has access to an inventory of items, each with a unique ID. We could represent that inventory with the following types:
178
+
179
+ ```ts
180
+ export interface InventoryItem {
181
+ id: string;
182
+ name: string;
183
+ }
184
+
185
+ // An `Inventory` is a collection of `InventoryItem` objects keyed by their ID
186
+ export type Inventory = Record<string, InventoryItem>;
187
+ ```
188
+
189
+ We could just fetch the entire list of inventory items and put them in our local application store, but this would not be feasible if our inventory is large and contains thousands of items.
190
+
191
+ Instead, we can use a collection synchronizer to fetch only the items the user needs access to on a case-by-case basis as they progress through the application.
192
+
193
+ First, let's create a new state definition to represent the inventory:
194
+
195
+ ```ts
196
+ import { SyncState } from 'ngxs-synchronizers';
197
+
198
+ export interface InventoryItem {
199
+ id: string;
200
+ name: string;
201
+ }
202
+
203
+ export type Inventory = Record<string, InventoryItem>;
204
+
205
+ @SyncState<Inventory>({
206
+ name: 'inventory',
207
+ defaults: null
208
+ })
209
+ @Injectable()
210
+ class InventoryState {
211
+ ...
212
+ }
213
+ ```
214
+
215
+ Now, let's create a collection synchronizer for our new `inventory` state. Assume we have an `ItemInventory` Angular service that can retrieve individual inventory items from our remote data source:
216
+
217
+ ```ts
218
+ import { CollectionSynchronizer } from 'ngxs-synchronizers';
219
+
220
+ @Injectable({ providedIn: 'root' })
221
+ export class InventorySynchronizer implements CollectionSynchronizer<Inventory> {
222
+
223
+ // itemInventory is an existing service for retrieving inventory items from the backend
224
+ constructor(private readonly itemInventory: ItemInventory) {}
225
+
226
+ public read(_inventory: Inventory, options: CollectionSynchronizer.ReadOptions<Inventory>): Observable<InventoryItem> {
227
+ // Get the inventory item by the specified ID:
228
+ const itemId = options.propertyName;
229
+ return this.itemInventory.getItem(itemId);
230
+ }
231
+ }
232
+ ```
233
+
234
+ When a specific `InventoryItem` is requested by ID, our `InventorySynchronizer` will look up the specified item from the backend. Now we need to register our collection synchronizer with our `InventoryState` from earlier:
235
+
236
+ ```ts
237
+ @SyncState<Inventory>({
238
+ name: 'inventory',
239
+ defaults: null,
240
+ // The `InventorySynchronizer` is used to synchronize all of the fields in this state
241
+ synchronizers: InventorySynchronizer
242
+ })
243
+ @Injectable()
244
+ class InventoryState {
245
+ ...
246
+ }
247
+ ```
248
+
249
+ `InventorySynchronizer` will now be used to handle any requests for inventory items.
250
+
251
+ ## Using a collection synchronizer
252
+
253
+ Using a collection synchronizer is very similar to using a property synchronizer, except that the properties being synced will be items of the collection that the synchronizer manages.
254
+
255
+ Assume we have a page in our application that is used to show the details about a specific `InventoryItem`. We will need to look up the item from the backend when the user navigates to the page.
256
+
257
+ To do this, we'll invoke our `InventorySynchronizer`:
258
+
259
+ ```ts
260
+ import { Route } from "@angular/router";
261
+
262
+ @Component({...})
263
+ export class InventoryItemPage {
264
+
265
+ public item: InventoryItem;
266
+
267
+ constructor(store: SyncStore, route: Route) {
268
+ // Retrieve the `itemId` param from the page route
269
+ route.params.pipe(
270
+ map(params => params.itemId),
271
+ switchMap((itemId: string) => {
272
+ // Fetch the specified item from the backend using the `InventorySynchronizer`
273
+ return store.state<Inventory>(InventoryState)
274
+ .syncProperty(itemId);
275
+ })
276
+ ).subscribe(item => this.item = item);
277
+ }
278
+ }
279
+ ```
280
+
281
+ When we call `syncProperty` with the specified `itemId`, the `read` method from `InventorySynchronizer` will be invoked and retrieve the given item from the backend.
282
+
283
+ As with property synchronizers, we could instead use `requireProperty` instead of `syncProperty` to only make a new request to the backend if the item data is not already in our local application store:
284
+
285
+ ```ts
286
+ store.state<Inventory>(InventoryState)
287
+ .requireProperty(itemId);
288
+ ```
289
+
290
+ ## Defining a state synchronizer
291
+
292
+ A state synchronizer is a special kind of property synchronizer that invokes all of the synchronizers defined for a given state. This allows you to call all of the synchronizers on a given state at once. This is useful for creating aggregate synchronizers for child states.
293
+
294
+ Recall our `SessionState` definition from earlier:
295
+
296
+ ```ts
297
+ import { SyncState } from 'ngxs-synchronizers';
298
+
299
+ interface Session {
300
+ username: string;
301
+ messages: string[];
302
+ }
303
+
304
+ @SyncState<Session>({
305
+ name: 'session',
306
+ defaults: null,
307
+ synchronizers: {
308
+ messages: MessagesSynchronizer
309
+ }
310
+ })
311
+ @Injectable()
312
+ class SessionState {
313
+ ...
314
+ }
315
+ ```
316
+
317
+ Now, we will create a new parent state called `ApplicationState`, which will have the `SessionState` as a child:
318
+
319
+ ```ts
320
+ import { SyncState } from 'ngxs-synchronizers';
321
+
322
+ interface AppData {
323
+ session: Session;
324
+ }
325
+
326
+ @SyncState<AppData>({
327
+ name: 'app',
328
+ defaults: null,
329
+ children: [SessionState]
330
+ })
331
+ @Injectable()
332
+ class AppState {
333
+ ...
334
+ }
335
+ ```
336
+
337
+ What if we wanted to synchronize our entire `session` state by invoking a synchronizer from the `AppState`? With state synchronizers, we can do just that.
338
+
339
+ First, create a new synchronizer called `SessionSynchronizer`:
340
+
341
+ ```ts
342
+ import { Injector } from '@angular/core';
343
+ import { StateSynchronizer } from 'ngxs-synchronizers';
344
+
345
+ @Injectable({ providedIn: 'root' })
346
+ export class SessionSynchronizer extends StateSynchronizer<AppData, "session"> {
347
+
348
+ constructor(injector: Injector) {
349
+ // Use the child `SessionState`
350
+ super(injector, SessionState);
351
+ }
352
+ }
353
+ ```
354
+
355
+ `SessionSynchronizer` extends the `StateSynchronizer` class, which handles automatically invoking all child synchronizers of a given state definition. In this example, we are creating a state synchronizer for `SessionState`, which in turn will invoke the `MessageSynchronizer` for the `messages` field (and any other synchronizers defined in that state).
356
+
357
+ This behavior can be customized by overriding the `read` method of the synchronizer and using the `StateSynchronizer.readSubset` method to invoke specific synchronizers.
358
+
359
+ With our `SessionSynchronizer` defined, we need to register it with our `AppState` definition:
360
+
361
+ ```ts
362
+ @SyncState<AppData>({
363
+ name: 'app',
364
+ defaults: null,
365
+ synchronizers: {
366
+ // Register the `SessionSynchronizer`
367
+ session: SessionSynchronizer
368
+ }
369
+ children: [SessionState]
370
+ })
371
+ @Injectable()
372
+ class AppState {
373
+ ...
374
+ }
375
+ ```
376
+
377
+ Now when we synchronize on the `session` property in `AppState`, all of the synchronizers defined in `SessionState` will be invoked automatically.
378
+
379
+ ## Using a state synchronizer
380
+
381
+ Using a state synchronizer is identical to using a property synchronizer:
382
+
383
+ ```ts
384
+ store.state<AppData>(AppState)
385
+ .syncProperty('session');
386
386
  ```