2.4.0 • Published 5 years ago

@purtuga/observables v2.4.0

Weekly downloads
-
License
MIT
Repository
github
Last release
5 years ago

observables

Enable the ability for Arrays and Objects to notify listeners when their content changes. Use it to create reactive interfaces or trigger logic when data changes. In addition, Computed Properties - properties whose value is generated when dependencies changes - can also be created.

Installation

$ npm install @purtuga/observables --save

Observe Objects

Observable Objects are normal JavaScript Objects whose properties can be "watched" for changes.

import { objectWatchProp } from "observables"

const user = {
    firstName: "Paul", 
    lastName: "Tavares" 
};
const unWatchFirst = objectWatchProp(user, "firstName", () => console.log("first named changed"));

user.firstName = "Jack"; // console: "first named changed"

// Stop listening to event
unWatchFirst();

To get notified of all changes to the object, leave the second param empty

objectWatchProp(user, null, () => console.log("Object changed."));

Observe Arrays

Arrays, like object, can be observed for changes. Note however, that due to the nature of Arrays in javascript, not all changes can be captured - example: direct array member creation (arr[arr.length] = 'new value')can not be detected. Only changes that are done via the Array's mutating methods will notify Watchers of changes.

import { watchArray } from "observables"

const users = [];
const unWatchUsers watchArray(users, () => console.log("array changed"));

users.push("Paul", "Jack"); // Console: "array changed"

// Stop listening for changes
unWatchUsers();

Observed Arrays size Property

Arrays that are made Observable will also have a new Array property (read only) named size which return the size (length) of the array with one added value: any dependency trackers are automatically set as Watchers of the array. This makes it ideal to use with Computed Properties (see below)

import { watchArray } from "observables"

const users = [];
watchArray(users);

users.push(1, 2);
console.log(users.size); // Console: 2

Convert Objects/Arrays to Observables

To recursively convert objects and arrays to observables, use the makeObservable method. This is ideal when wanting to create computed properties (see below) that may have dependencies comming from multiple objects or arrays.

import { makeObservable } from "observable"

const obj = {
    key1: {
        key1_1: {
            key1_1_1: "value 1-1-1"
        },
        keys1_2: [
            {
                k1: "v1"
            }
        ]
    }
};

makeObservable(obj); // Makes all Objects/Arrays observable

Create Computed Properties

Computed properties allow you to create properties whose value is generated via a callback function. If that callback function, in its logic, uses properties from any observable property, then it will notify its watchers and cause the value to be regenerated. Computed properties, like other observable object properties, can be watched for changes.

import {
    objectCreateComputedProp,
    makeObservable
} from "observables"

const user = {
    firstName: "Paul", 
    lastName: "Tavares" 
};

makeObservable(user);

// Create `fullName` computed property
objectCreateComputedProp(user, "fullName", () => `${ user.firstName } ${ user.lastname }`);

console.log(user.fullName); // Console: Paul Tavares

user.firstName = "Jack";
console.log(user.fullName); // Console: Jack Tavares

Computed properties also work with Arrays that have been converted to observables. Here is an example:

import {
    objectCreateComputedProp,
    makeObservable
} from "observables"

const user = {
    firstName: "Paul", 
    lastName: "Tavares",
    permissions: [
        "view",
        "create"
    ]
};

makeObservable(user);

objectCreateComputedProp(user, "summary", () => `${ user.firstName } ${ user.lastname } Has ${ user.permissions.size } Permission(s)`);

console.log(user.summary); // Console: "Paul Tavares Has 2 Permission(s)"

user.permissions.push("modify");
console.log(user.summary); // Console: "Paul Tavares Has 3 Permission(s)"

Decorators

The following decorators are available under src/decorators (but not in the built module).

NOTE: Decorators (as of this writing) are at Stage 2 and continue to undergo heavy changes to its spec. The decorators provided here are supported only in Build chains that use Babel + @babel/plugin-proposal-decorators plugin with the legacy options set to false. They are based from a spec version that was valid around the start of 2019 (this one) If you are using Typescript, this is likely not to work since the version of Decorators Typescript supports is an erlier spec version. For more about decorators, see https://github.com/tc39/proposal-decorators#faq

DependencyTracker Class Decorator

Intercept Class methods and setup observable dependency trackers prior to executing the method.

Example:

import {makeObservable} from "@purtuga/observables";
import {trackObservableDependencies} from "@purtuga/observables/src/decorators/DependencyTracker.js"

//============================================================

@trackObservableDependencies({
    track: {
        // When `getValue()` method is called, adds `_updateValue` as a change notifier
        // for any observable data that is accessed
        getName() { return this._updateValue; }
    },
    stop: {
        // When `destroy()` is called, it will remove `_updateValue` method from any
        // Observable that has it as a change notifier 
        destroy() {
            return this._updateValue;
        }
    }
})
class Person {
    constructor(data) {
        this._data = makeObservable(data);
    }
    
    _value = "";
    _updateValue = () => this._value = `${this._data.first} ${this._data.last}`;
    
    getName() {
        return this._value;
    }
}

const data = {
    first: "Paul",
    last: "Tavares"
};
const person = new Person(data);

console.log(person.getName()); // Paul Tavares

data.first = "Bill";
data.last = "Smith";

console.log(person.getName()); // Bill Smith

This decorator takes the following options:

  • track: {Object} The methods that will be intercepted. The Object's Key is the method name and its value must be a function that returns the callback that will be used as the dependency tracker notifier. The Function will be called with a context (this) of the Class instance and will also be given that class instance as the first argument. NOTE: the returned method should probably (in most cases) be a class bound method since it will be called as if it was a "global" function not a method.
  • stop: {Object} The methods that will be intercepted in order to remove all dependency track notification from a tracker notifier. Just like the above options, this object's key value must be a function that will be called with the class instance.

License

MIT License