Skip to content

Commit

Permalink
0.2.0:
Browse files Browse the repository at this point in the history
- (Breaking) Improved API for more intuitive usage/better type correctness
- Expanded documentation
  • Loading branch information
lVlyke committed Aug 4, 2019
1 parent d5080b7 commit 0dc33bf
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 95 deletions.
34 changes: 17 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,21 @@ Easily keep your app's local state synchronized with your backend, databases and
* [**About**](#about)
* [**Features**](#features)
* [**Installation**](#installation)
* Configuration
* Using ngxs-synchronizers
* [**Quick start**](#quick-start)
* [**Tutorial**](/docs/usage-guide.md)
* API reference

## About

ngxs-synchronizers is an extension to NGXS that allows for easy synchronization between a local NGXS state and an external data source (backend service, database, JSON file, etc) through special Angular services called _Synchronizers_. ngxs-synchronizers can be used to fetch new data from an external data source, _require_ data that isn't already present in the app (allowing for zero-configuration caching), and can be used to listen to remote requests for easily managing complex UI interactions that rely on synchronization of asynchronous data requests.
**ngxs-synchronizers** is an extension to NGXS that allows for easy synchronization between a local NGXS state and an external data source (backend service, database, JSON file, etc) through special Angular services called _Synchronizers_. ngxs-synchronizers can be used to fetch new data from an external data source, _require_ data that isn't already present in the app (allowing for zero-configuration caching), and can be used to listen to remote requests for easily managing complex UI interactions that rely on synchronization of asynchronous data requests.

Check out the [quick start guide](#quick-start) if you're already familiar with NGXS and want to add ngxs-synchronizers to your existing app, or read through the [full tutorial](/docs/usage-guide.md) for a more thorough walkthrough.

## Features

* Easy to configure - Straightforward configuration that can be used accross your whole application or targeted at specific modules.
* Efficient - ngxs-synchronizers uses RxJS for efficient updating of data. Duplicate data requests are automatically batched into single requests and all synchronization requests can be observed and cancelled.
* Easy to integrate - ngxs-synchronizers integrates transparently with NGXS and makes it feel like part of NGXS instead of yet another library.
* **Easy to configure** - Straightforward configuration that can be used accross your whole application or targeted at specific modules.
* **Efficient** - ngxs-synchronizers uses RxJS for efficient updating of data. Duplicate data requests are automatically batched into single requests and all synchronization requests can be observed and cancelled.
* **Easy to integrate** - ngxs-synchronizers integrates transparently with NGXS and makes it feel like part of NGXS instead of yet another library.

## Installation

Expand All @@ -28,11 +29,9 @@ ngxs-synchronizers requires @ngxs/store as a dependency. Both can be installed f
npm install @ngxs/store ngxs-synchronizers
```

For configuration information, see the configuration guide.

## Quick start

This section assumes you have an existing NGXS-enabled application already set up. Please see the (configuration guide)[] and (usage guide)[] for more detailed usage information.
This section assumes you have an existing NGXS-enabled application already set up. Please see the [usage guide](/docs/usage-guide.md) for more detailed usage information.

After installing ngxs-synchronizers, we need to make some slight modifications to our existing ```@State``` classes. Given the following example NGXS ```State```:

Expand Down Expand Up @@ -67,17 +66,18 @@ class SessionState {
}
```

Next, we need to define at least one ```Synchronizer``` service to sync up with our theoretical backend service. We're going to write a synchronizer that gets the user's latest messages from the backend. Let's assume an existing service called ```Messages``` that contains a ```get``` method for retreiving the list of messages for a given username from the backend.
Next, we need to define at least one ```Synchronizer``` service to sync up with our theoretical backend service. We're going to write a property synchronizer that gets the user's latest messages from the backend. Let's assume an existing service called ```Messages``` that contains a ```get``` method for retreiving the list of messages for a given username from the backend.

### Synchronizer service example:

```ts
@Injectable()
export class MessagesSynchronizer implements Synchronizer<Session, 'messages'> {
export class MessagesSynchronizer implements PropertySynchronizer<Session, 'messages'> {

public readonly property = 'messages';
public readonly requiredProperties = ['username'];

// messagesProvider is an existing service for retrieving user messages from the backend
constructor(private readonly messagesProvider: Messages) {}

public read({ username }: Session): Observable<string[]> {
Expand All @@ -87,7 +87,7 @@ export class MessagesSynchronizer implements Synchronizer<Session, 'messages'> {
}
```

Our newly created ```MessagesSynchronizer``` service implements the ```Synchronizer``` interface and specifies that it's managing the ```messages``` field 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 case we're assuming this value already exists in our local state, but we could also create a ```Synchronizer``` for the ```username``` field and it would automatically be invoked when we go to request new messages through this synchronizer.
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 case we're assuming this value already exists in our local state, but we could also create a ```Synchronizer``` for the ```username``` field and it would automatically be invoked when we go to request new messages through this synchronizer.

The ```read``` method we've implemented receives any required fields from the object that are needed to perform the request. Since we've specified we require the ```username``` field, the ```read``` method will receive a partial ```Session``` object that contains the current value for ```username``` in our local store. The 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[]```.

Expand Down Expand Up @@ -120,17 +120,17 @@ export class MessagesPage {
constructor(store: SyncStore) {
// Fetch the latest messages from the backend
store.state<Session>(SessionState)
.syncMetadata<'messages'>('messages')
.syncProperty('messages')
.subscribe(messages => this.messages = messages);
}
}
```

We call ```SyncStore.state``` to get the ```StateSelector``` corresponding to our ```SessionState```. Calling ```StateSelector.syncMetadata``` uses the ```MessagesSynchronizer``` we defined earlier to get the latest messages from the backend.
We call ```SyncStore.state``` to get the ```StateSelector``` corresponding to our ```SessionState```. Calling ```StateSelector.syncProperty``` uses the ```MessagesSynchronizer``` we defined earlier to get the latest messages from the backend.

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 ```syncMetadata``` with ```requireMetadata```, which will only make a request to the backend if the data doesn't already exist in the local store. Otherwise it will just return the existing data in the store.
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 doesn't already exist in the local store. Otherwise it will just return the existing data in the store.

### ```requireMetadata``` example:
### ```requireProperty``` example:

```ts
@Component({...})
Expand All @@ -141,7 +141,7 @@ export class MessagesPage {
constructor(store: SyncStore) {
// Fetch the latest messages from the backend
store.state<Session>(SessionState)
.requireMetadata<'messages'>('messages')
.requireProperty('messages')
.subscribe(messages => this.messages = messages);
}
}
Expand Down
2 changes: 1 addition & 1 deletion coverage/coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions docs/usage-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# NGXS Synchronizers Usage Guide

Coming soon...
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ngxs-synchronizers",
"version": "0.1.0",
"version": "0.2.0",
"description": "Easily keep your app's local state synchronized with your backend, databases and more! ngxs-synchronizers simplifies synchronizing your NGXS-based application state with external data sources.",
"author": "Mychal Thompson <mychal.r.thompson@gmail.com>",
"license": "MIT",
Expand Down
4 changes: 2 additions & 2 deletions spec/test.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Synchronizer } from "./../src/synchronizer";
import { PropertySynchronizer } from "./../src/synchronizer";


describe("Test", () => {
it("works", () => expect(Synchronizer).toBeTruthy());
it("works", () => expect(PropertySynchronizer).toBeTruthy());
});
Loading

0 comments on commit 0dc33bf

Please sign in to comment.