Skip to content

Commit

Permalink
updating readme
Browse files Browse the repository at this point in the history
  • Loading branch information
Pat Sissons committed Sep 28, 2016
1 parent b3e0b1f commit 91cc132
Showing 1 changed file with 88 additions and 36 deletions.
124 changes: 88 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,28 @@ This library is built using a few very basic components that are layered upon ea
This is the top level component. Extend this class to start using `rxobj`. In your subclass, use the following instance methods to create reactive members:

* `property(initialValue?)` creates a reactive **state** property (a property whose value is modified in code)
* `propertyFrom(source, initialValue?)` creates a reactive **stream** property (a property whose value is modified by an observable source)
* `command(executeAction, canExecuteObservable?)` creates a reactive reactive command (gated function execution)
* `list(items?)` creates a reactive list (array with modification notifications)

All reactive members (and the `ReactiveObject` itself) are `ReactiveState` instances, with `changing` and `changed` observable properties. Each observable will bubble events up to the owning `ReactiveObject` instance. You can subscribe to either observable property to receieve notifications of changes.
Each of these reactive properties are strongly typed with generic parameters. `ReactiveProperty`'s and `ReactiveList`'s are generically typed by the values they store, and `ReactiveCommands`'s are generically typed by the execution result and the input parameters.

Additionally, properties that are themselves `ReactiveState`'s may be registered onto a `ReactiveObject` using the `registerMember(member)` function. This is particularly useful to chain observable events on child `ReactiveObject` instances.

### ReactiveState

This is a base level class that holds all the reactive state and controls how notifications are generated. Both `ReactiveObject` and `ReactiveProperty` extend this class.
This is a base level class that holds all the reactive state and controls how notifications are generated. All reactive members are `ReactiveState` instances, with `changing` and `changed` observable properties. Each observable will bubble events up to any owning `ReactiveObject` instance. You can subscribe to either observable property to receieve notifications of changes. All `ReactiveState` instances will always return true when accessing the `isReactive` property.

`ReactiveState` objects are strongly typed against their owning object (typically a `ReactiveObject`), the **value** of the state, and the event structure that is emitted for both `changing` and `changed` observables. The owning object may be omitted for top level reactive states (i.e., a top level `ReactiveObject`).

Access `changing` or `changed` `Observable` properties to wire up event streams. `changing` events always happen before the change occurs, and `changed` events always happen after the change occurs.
Access the `changing` or `changed` `Observable` properties to wire up event streams. `changing` events always happen before the change occurs, and `changed` events always happen after the change occurs.

subscribe to the `thrownErrors` `Observable` to see what errors have been handled within the `ReactiveState`.

Use `suppressChangeNotifications` to stop all notifications from happening, or `delayChangeNotifications` to redirect all notifications to a temporary buffer that will be flushed at your command.
Use `suppressChangeNotifications` to stop all notifications from happening, or `delayChangeNotifications` to redirect all notifications to a temporary buffer that will be flushed at your command. When delaying notifications, once the delay is disabled and the events are flushed, all events will be *de-duplicated* to prevent redundant events. For `ReactiveObjects` instances, the *de-duplication* process will remove events with duplicate state `name` values. For all other `ReactiveState` instances, *de-duplication* simply removes identical adjacent values.

`ReactiveState` instances will try to automatically detect their own property name (via their owning `ReactiveObject`) if possible. This `name` property can be accessed once any event has occurred for the `ReactiveState`. ***NOTE*** that you may only assign the `name` property once, if you must perform manual name assignment then be sure not to assign more than once.

`ReactiveState` instances will attempt to keep track of their *current* `value`, which by default is their most recent `changed` event value. The `ReactiveState` based classes in this library support providing a more meaningful `value` for the state. This `value` property is used in the `whenAnyValue` function/augumentation.

### ReactiveEvent

Expand All @@ -46,58 +53,103 @@ All notifications come in the form of a `ReactiveEvent`. Each event has a source

This static class holds the `defaultErrorHandler` and the `mainScheduler`. Feel free to override the `defaultErrorHandler` to perform custom global error handling (default is simply `console.error`).

### Augmentations

This library augments some classes with utility functions to simplify some common development strategies.

#### Observable Augmentations

Given an `Observable<Param>` instance we can execute `invokeCommand` to chain an observable to a command execution.

* `invokeCommand(ReactiveObject, ReactiveCommand): Subscription`
* `invokeCommand(ReactiveObject, (ReactiveObject, Param) => ReactiveCommand): Subscription`

The first signature is for executing a static command, the second is for executing a dynamic command (i.e. a command that can be dependent on the value of the observable, the parameter). The result of this function is the resulting Subscription that represents the observable connection.

Given an `Observable<T>` instance we can execute `pausableBuffer` to create a *pauseable* buffered observable.

* `pausableBuffer(Observable<boolean>, (T[]) => T[]): Observable<T>`

This function accepts an observable which determines if the buffer is paused or not, and a delegate to support accumulated value translation when the buffer is *unpaused* (this is used to *de-duplicate* results in `ReactiveState`).

Given an `Observable<TValue>` instance we can execute `toProperty` to produce a read only `ReactiveProperty` instance that is driven by the observable stream.

* `toProperty(ReactiveObject, TValue?): ReactiveProperty`

This function allows you to inject an initial value before the source observable has even produced any events so that the resulting `ReactiveProperty` may contain an initial value instead of `undefined`.

#### Array Augmentations

Given an `Array<T>` instance we can execute `toList` to produce a `ReactiveList` instance.

* `toList(ReactiveObject): ReactiveList`

#### ReactiveObject Augmentations

These augmentations are available both as instance methods on any ReactiveObject as well as static methods applied to the API surface. All of these augmentations take a source state as the first parameter and then produce some observable result. All three of these augmentation variants support a special signature that simply watches the source state for changes and produces the observable result using a delegate function for that source.

* `whenAnyObservable(ReactiveState, (ReactiveState) => TResult): Observable<TResult>`
* `whenAnyObservable(ReactiveState, (ReactiveState) => Observable<T1>, (T1) => TResult): Observable<TResult>`
* `whenAnyObservable(ReactiveState, (ReactiveState) => Observable<T1>, (ReactiveState) => Observable<T2>, (T1, T2) => TResult): Observable<TResult>`

`whenAnyObservable` functions require that each member delegate returns an observable stream. The combination of values for each stream event will then be remapped to an observable result. ***NOTE*** that due to this variant using observables directly, no results will be generated until all members have generated at least one event. This means that when using this augmentation you may want to use a `startWith` at the end of your observable member composition.

* `whenAnyState(ReactiveState, (ReactiveState) => TResult): Observable<TResult>`
* `whenAnyState(ReactiveState, (source: TSource) => T1, (T1) => TResult): Observable<TResult>`
* `whenAnyState(ReactiveState, (source: TSource) => T1, (source: TSource) => T2, (T1, T2) => TResult): Observable<TResult>`

`whenAnyState` functions require that each member delegate returns a `ReactiveState` value. This variant will return a result immediately and the mapping function will be passed each member `ReactiveState` for projection. This variant is useful if there is a need project internals of the `ReactiveState` as part of the result observable stream.

* `whenAnyValue(ReactiveState, (ReactiveState) => TResult): Observable<TResult>`
* `whenAnyValue(ReactiveState, (source: TSource) => T1, (T1) => TResult): Observable<TResult>`
* `whenAnyValue(ReactiveState, (source: TSource) => T1, (source: TSource) => T2, (T1, T2) => TResult): Observable<TResult>`

`whenAnyValue` functions require that each member delgate returns a `ReactiveState` value. This is the most common variant as it will automatically project the `ReactiveState`'s `value` property prior to the final projection delegate. This means that when generating a result observable stream, only the state values are provided. This variant also returns a result immediately, using the current value of each `ReactiveState` member as the initial value.

## Example

A simple example based on the [ReactiveUI](http://reactiveui.net/) example.

```ts
interface SearchService {
getResults(query: string): string[];
}
import { Observable } from 'rxjs';
import * as rxo from 'rxobj';

interface ErrorHandler {
handleError(error: Error): void;
export interface SearchService {
getResults(query: string): Observable<string[]>;
}

class SearchViewModel extends ReactiveObject {
public queryText: ReactiveValueProperty<this, string>;
public search: ReactiveCommand<this, string[]>;
public searchResults: ReactiveList<this, string>;
export class SearchViewModel extends rxo.ReactiveObject {
public queryText: rxo.ReactiveProperty<this, string>;
public search: rxo.ReactiveCommand<this, string, string[]>;
public searchResults: rxo.ReactiveList<this, string>;

constructor(private searchService: SearchService, private errorHandler: ErrorHandler) {
constructor(private searchService: SearchService) {
super();

this.queryText = this.property('');
this.searchResults = this.list<string>();

const canSearch = this.queryText.changed
.map(x => x.value)
.map(x => x != null && x.trim().length > 0)
const canSearch = this
.whenAnyValue(this, (x: this) => x.queryText, x => x != null && x.trim().length > 0)
.distinctUntilChanged()
.startWith((this.queryText.value || '').length > 0);

this.search = this.command(() => {
return this.searchService.getResults(this.queryText.value);
}, canSearch);

this.searchResults = this.list<string>();
this.search = this.command((queryText: string) => this.searchService.getResults(queryText), canSearch);

this.search.results
.subscribe(x => {
this.searchResults.clear();
this.searchResults.push(...x);
});
this.add(
this.search.results
.subscribe(x => {
this.searchResults.reset(...x);
})
);

this.search.thrownErrors
.subscribe(x => {
this.errorHandler.handleError(x);
});
.subscribe(this.thrownErrorsHandler.next);

this.queryText.changed
.map(x => x.value)
this
.whenAnyValue(this, (x: this) => x.queryText, x => x)
.debounceTime(1000)
.subscribe(() => {
this.search.execute();
});
.invokeCommand(this, (x: this) => x.search);
}
}
```
Expand Down

0 comments on commit 91cc132

Please sign in to comment.