When starting with the following json graph
{
lists: {
2343: {
0: { $type: "ref", value: ["videos", 123] },
1: { $type: "ref", value: ["videos", 123] }
}
},
videos: {
123: {
name: { $type: "atom", value: undefined }
}
}
}
A get
would not emit values for intermediate branches found in the cache, unless an atom was found
const json = await model.get(["lists", 2343, {to: 1}, "name"]);
{
json: {
{
lists: {
2343: {
0: {}
}
}
}
}
}
Where now, any branches or references found in the cache will always be emitted in the json output
const json = await model.get(["lists", 2343, {to: 1}, "name"]);
{
json: {
lists: {
2343: {
0: {},
1: {}
}
}
}
}
That means that even requesting no paths will emit an empty object, as the cache root will be found
const json = await model.get();
{
json: {}
}
The only case where get won't onNext at least one value is when it receives only errors from the underlying data source.
get
no longer emits references as leaf values.
When starting with the following json graph
{
lists: {
2343: {
0: { $type: "ref", value: ["videos", 123] }
}
},
videos: {
123: {
name: "House of cards"
}
}
}
Previously the following would emit a ref
const json = await model.get(["lists", 2343, "0"]);
{
lists: {
2343: {
0: ["videos", 123]
}
}
}
Where now, a key with undefined is emitted
const json = await model.get(["lists", 2343, "0"]);
{
lists: {
2343: {
0: undefined
}
}
}
Documentation on ModelResponse is found here
In 0.x ModelResponse
's prototype inherited from Rx.Observable
in the
following way.
var Rx = require('rx/dist/rx');
var ModelResponse = function ModelResponse(...) {...};
ModelResponse.prototype = Object.create(Rx.Observable.prototype);
...
This means that after a get
, set
, or call
any Rx
operator could be used.
E.G.
model.
get(['hello', 'falcor']).
doAction(function(x) {
...
}).
flatMap(function(x) {
return model.get(...);
}).
subscribe(...);
If your application relies on that behavior there are two possible upgrade
paths. If your application does not rely on Rx
, but only the subscribe
from
Observable
then nothing has changed except for file size.
Alter the prototype for get
, set
, and call
to return Rx.Observables
.
var Rx = require('rx');
var falcor = require('./lib');
var Model = falcor.Model;
var slice = Array.prototype.slice;
var noRx = {
get: Model.prototype.get,
set: Model.prototype.set,
call: Model.prototype.call
};
Model.prototype.get = function getWithRx() {
var args = slice.call(arguments, 0);
return convertToRx(this, 'get', args);
};
Model.prototype.set = function setWithRx() {
var args = slice.call(arguments, 0);
return convertToRx(this, 'set', args);
};
Model.prototype.call = function callWithRx() {
var args = slice.call(arguments, 0);
return convertToRx(this, 'call', args);
};
function convertToRx(model, method, args) {
return Rx.Observable.create(function(observer) {
return noRx[method].apply(model, args).subscribe(observer);
});
}
- This upgrade only has to be done once and required once for the whole application to receive the benefits.
- In the same vein, the whole application is forced into using the Rx based falcor whether it wants to or not since the prototype has been edited.
Wrap all calls to falcor with a Falcor.Subscribable -> Rx.Observable call.
var Rx = require('rx');
var Observable = Rx.Observable;
module.exports = function toObservable(response) {
return Observable.create(function(observer) {
return response.subscribe(observer);
});
};
...
// Don't forget to wrap the progressively() call as well.
toObservable(
model.
get(['my', 'path']).
progressively()).
doAction(function() {
// Something awesome goes here
}).
subscribe();
- Its a more controlled approach since its opt-in only.
- This has to be done everywhere a call to falcor is made and Rx is the desired
output format.
- can be a bit tedious :)
Documentation on deref is found here
deref
has changed to use the output from a ModelResponse
instead of
specifying the destination via path and leaves. This means that there will be
problems if you rely on Object.keys
to iterate over your json
. Instead,
use falcor.keys
. It will strip out the $__path
from the ModelResponse's
json
.
Lets create a model with some initial cache.
var model = new Model({
cache: {
genreLists: {
0: Model.ref(['lists', 'A'])
},
lists: {
A: {
1337: Model.ref(['videos', 1337])
}
},
videos: {
1337: {
title: Model.atom('Total Recall (June 1st, 1990)')
}
}
}
});
If we were to use deref
in the old way we would have to perform the
following to dereference to [genreLists, 0, 0].
model.
// Creates a dataSource (more than likely means a network call) call since
// imageUrl does not exist in the cache.
deref(['genreLists', 0, 0], ['title', 'imageUrl']).
subscribe(function(boundModel) {
// equivalent to model.get(['genreLists', 0, 0, 'title'])
// -> { json: { title: 'Total Recall (June 1st, 1990)' } }
boundModel.get(['title'])...
// Other rendering stuff / application logic
...
});
- The knowledge of leaves were required.
- Potential additional network requests could be made.
- Always async.
- Not very simple to explain how this works.
The new deref
works from the output of ModelResponse
, so the same thing could be
accomplished with the following.
model.
get(['genreLists', 0, 0, 'title']).
subscribe(function(x) {
var json = x.json;
var boundModel = model.deref(json.genreLists[0][0]);
// equivalent to model.get(['genreLists', 0, 0, 'title'])
// -> { json: { title: 'Total Recall (June 1st, 1990)' } }
// If 'imageUrl' is used then a dataSource call would be made.
boundModel.get(['title'])...
// Other rendering stuff / application logic
...
});
- Simpler to grok/use
- Promotes better application architecture.
- Always synchronous.
In 0.x we depend on the 'promise' npm package to supply a Promise implementation on platforms missing the Promise builtin. In 1.x the choice of Promise shim is made at bundle build time. The supplied bundles are built with the same 'promise' npm package.
To replicate this in your own Browserify build use the insertGlobalVars
browserify option to use the Promise shim of your choice:
browserify(filename, {
insertGlobalVars: {
Promise: function (file, basedir) {
return 'typeof Promise === "function" ? Promise : require("promise")';
}
}
}
With Webpack we can use ProvidePlugin
to the same effect:
var path = require("path");
var webpack = require("webpack");
module.exports = {
plugins: [
new webpack.ProvidePlugin({
Promise: path.join(__dirname, "promise-implementation"),
})
]
};
Where promise-implementation.js is:
module.exports = global.Promise || require("promise");
For those not using Falcor's Promise functionality (i.e. model.get().then(...)
)
or deploying only to modern browsers, omitting the 'promise' package from your
build will save ~1KB page weight from your build after gzipping and minification.