-
Notifications
You must be signed in to change notification settings - Fork 77
FAQ and How Tos
How Tos:
How Tos are solutions to common problems. They may not be the best for every circumstance (or any circumstance), so take them with a grain of salt.
Say you have two resources, users
and posts
. Let's also say that the server returns the post
resource with a user
property that's actually the user
object. The problem is that now you have two resources loaded, but only one injected (the post). And if you are using the methods
property when you defined your user
resource, the post.user
will not be of type User
and you can't invoke any of the methods on that user.
Angular-data gives you the afterInject
hook which can help to resolve this issue. The concept is pretty simple. After the post is injected, you tell DS
to inject the user and replace the reference to the user in the post to the new injected user. You can do this like so:
DS.defineResource({
name: 'user',
endpoint: 'users',
methods: {
sayHi: function sayHi() {
console.log('Hi! My name is ' + this.name);
}
}
});
DS.defineResource({
name: 'post',
endpoint: 'posts',
afterInject: function afterInject(resourceName, attrs) {
attrs.user = DS.inject('user', attrs.user);
};
});
Now you can do post.user.sayHi()
and everything will work splendidly.
If you have several resources with this same issue, you may consider implementing something more generic. Like this:
DS.defaults.afterInject = function defaultAfterInject(resourceName, attrs) {
findAndInjectChildResources(attrs);
};
// All resources should be defined at this point
var resourceNames = Object.keys(DS.definitions);
function findAndInjectChildResources(value, key, parent) {
if (angular.isObject(value)) {
if (value.id && key) {
if (_.contains(resourceNames, key)) { // using lodash
parent[key] = DS.inject(key, parent[key]);
} else if (angular.isArray(value) && _.contains(resourceNames, pluralize.singular(key))) { // using pluralize, to handle cases like "mouse" as a single resource or "mice" as an array of resources
parent[key] = angular.forEach(value, function(val, index) {
parent[key][index] = DS.inject(key, val);
});
}
} else {
value = angular.forEach(value, function(val, k) {
value[k] = findAndInjectChildResources(val, k, value);
});
}
}
if (parent && key) {
return parent[key];
} else {
return value;
}
}
Note: if the service you're using is sending back author
on the post
instead of user
, the generic solution above wont work and you may need to one-off that piece.
Another Note: if the service is sending back only part of the child resource (like a "basic - this is all you need in this context" type thing) then you shouldn't be injecting the resource at all because you'll run into issues with caching and you could instead alter the previous solution like so:
// All resources should be defined at this point
var resourceNames = Object.keys(DS.definitions);
angular.forEach(resourceNames, function(resourceName) {
// get the Resource's Class (if it has one, otherwise, just use Object) and use it to create new instances.
var ResourceClass = DS.definitions[resourceName][DS.definitions[resourceName].class] || Object;
DS.definitions[resourceName].meta = DS.definitions[resourceName].meta || {};
DS.definitions[resourceName].meta.createInstance = function(attrs) {
var instance = new ResourceClass(); // note you have a reference to the resource definition using "this" here, so you could also do: new this[this.class]();
return angular.extend(instance, attrs);
};
});
Then you alter the above example to call createInstance
on the definition rather than injecting the resource into the datastore.