can/ref
Handle references to instances in the data returned by the server. Allows several means of loading referenced instances, determined on-the-fly.
canRef( baseConnection )
Adds a reference type to connection.Map
that loads the related type or holds onto
an existing one. This handles circular references and loads relevant data as needed. The reference type can be loaded
by:
- it's data being included in the response for the referencing instance
- having an existing instance available in the instanceStore
- lazy loading via the connection for the reference type
Parameters
- baseConnection
{Object}
:can-connect
connection object that is having thecan/ref
behavior added on to it. Expects the can/map behavior to already be added to this base connection. If theconnect
helper is used to build the connection, the behaviors will automatically be ordered as required.
Returns
{Object}
:
a connection with the Map
having the reference type property
(Map.Ref.type
) created by can/ref
.
Use
can/ref
is useful when the server might return either a reference to
a value or the value itself. For example, in a MongoDB setup,
a request like GET /game/5
might return:
{
id: 5,
teamRef: 7,
score: 21
}
But a request like GET /game/5?$populate=teamRef
might return:
{
id: 5,
teamRef: {id: 7, name: "Cubs"},
score: 21
}
can/ref
can handle this ambiguity and even make lazy loading possible.
To use can/ref
, first create a Map and a connection for the referenced type:
var Team = DefineMap.extend({
id: 'string'
});
connect([
require("can-connect/constructor/constructor"),
require("can-connect/constructor/store/store"),
require("can-connect/can/map/map"),
require("can-connect/can/ref/ref")
],{
Map: Team,
List: Team.List,
...
})
The connection is necessary because it creates an instance store which will
hold instances of Team
that the Team.Ref
type will be able to access.
Now we can create a reference to the Team within a Game map and the Game's connection:
var Game = DefineMap.extend({
id: 'string',
teamRef: {type: Team.Ref.type},
score: "number"
});
superMap({
Map: Game,
List: Game.List
})
Now, teamRef
is a Map.Ref type, which will
house the id of the reference no matter how the server returns data, e.g.
game.teamRef.id
.
For example, without populating the team data:
Game.get({id: 5}).then(function(game){
game.teamRef.id //-> 7
});
With populating the team data:
Game.get({id: 5, $populate: "teamRef"}).then(function(game){
game.teamRef.id //-> 7
});
The values of other properties and methods on the Map.Ref type are determined by if the reference was populated or the referenced item already exists in the instanceStore.
For example, value
, which points to the referenced instance, will be populated if the reference was populated:
Game.get({id: 5, $populate: "teamRef"}).then(function(game){
game.teamRef.value.name //-> 5
});
Or, it will be populated if that instance had been loaded through another means and it’s in the instance store:
Team.get({id: 7}).then(function(team){
// binding adds things to the store
team.on("name", function(){})
}).then(function(){
Game.get({id: 5}).then(function(game){
game.teamRef.value.name //-> 5
});
})
value
is an asynchronous getter, which means that even if
the referenced value isn't populated or loaded through the store, it can be lazy loaded. This
is generally most useful in a template.
The following will make an initial request for game 5
, but when the template
tried to read and listen to game.teamRef.value.name
, a request for team 7
will be made.
var template = stache("{{game.teamRef.value.name}} scored {{game.score}} points");
Game.get({id: 5}).then(function(game){
template({game: game});
});