can-define-connected-singleton
can-define-connected-singleton
Singleton decorator for can-define/map/map
Overview
This function is used to extend a DefineMap constructor so that a single instance of persisted data is easily referenced, very much like a singleton. This is useful for situations where you only want access to a single shared instance of a class whose data is loaded from a services. The primary use case for this is for referencing a user's session in a client application.
Usage
import singleton from 'can-define-connected-singleton';
import DefineMap from 'can-define/map/map';
// function wrapper (recommended)
const MyType = singleton(
DefineMap.extend({ ... })
);
// or as a *legacy* decorator
// SEE: https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy
const MyType = DefineMap.extend({ ... });
For a practical example of usage refer to the Managing Sessions data guide which uses a can-connect behavior that includes the functionality from this module.
How does it work
Once you have decorated your class, the class will have three new static properties:
MyType.current
- the current value for the singletonMyType.currentPromise
- the promise which should resolve to the value for the singletonMyType.saving
- the instance of the singleton currently active as part of an ongoing save request
The first time you read either current
or currentPromise
, the value will be loaded by calling the static get
method on your class. The get
method should return a Promise, and this promise will be stored on the static currentPromise
property of your class:
const MyType = singleton(
DefineMap.extend({ ... })
);
// define the static "get" method
// NOTE: the can-map behavior for can-connect does this for you
MyType.get = function() {
return Promise.resolve('the value');
}
// triggers a call to MyType.get()
MyType.current; //-> initially undefined
MyType.currentPromise.then(value => {
MyType.current === value; //-> true
});
If your service requires you to pass parameters as part of loading the singleton, e.g logging in to retrieve a session model, your application should use the save
method on an instance of your class at some point prior to use of current
or currentPromise
. The save
method should return a Promise, and this promise will be stored on the static currentPromise
property of the class. The instance being saved will be stored on the static saving
property of the class for the duration of the request:
const MyType = singleton(
DefineMap.extend({
// define the static "get" method
// NOTE: the can/map behavior for can-connect does this for you
get: function() {
return Promise.resolve('the value');
},
...
}, {
// define the instance "save" method
// NOTE: the can/map behavior for can-connect does this for you
save: function() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(this), 1000);
});
}
})
);
const instance = new MyType({ username: 'nils', password: 'foobar' });
const promise = instance.save();
MyType.current; //-> initially undefined, doesn't start a request since .save is ongoing
MyType.saving === instance; //-> true
MyType.currentPromise === promise; //-> .currentPromise is the is the ongoing save request
MyType.currentPromise.then(value => {
MyType.current === value; //-> true
MyType.current === instance; //-> true
MyType.saving; //-> undefined after the .save finished
});
Configuration options
By default, the singleton decorator uses the following options:
{
currentPropertyName: 'current',
savingPropertyName: 'saving',
fetchMethodName: 'get',
createMethodName: 'save',
destroyMethodName: 'destroy'
}
You can specify your own options using the following syntax:
const options = {
currentPropertyName: 'foo',
fetchMethodName: 'doFoo'
};
// as a function wrapper (recommended)
const MyType = singleton( options )(
DefineMap.extend({ ... })
);
// or as a decorator
const MyType = DefineMap.extend({ ... });
Using the above options, your class would be decorated with foo
and fooPromise
properties instead of current
and currentPromise
, respectively. Furthermore, the static doFoo
method will be invoked instead of the get
method for loading the singleton data.