CRUD with multiple models
Often, your CRUD pages will contain more complicated than just "single row
models". Consider, as an example, a ticketing system - a ticket is linked to a
submitter, followers, an assignee and perhaps other tickets or a version control
system. It would be a pain to have to write custom controllers for all such
items. Luckily, this is usually really simple in Monad: everything on the
data resolve is considered saveable by moUpdate!
Registering multiple data sources
Simply register what you need:
//...
$routeProvider.when('/some/url', {
data: ['moResource', '$route', function (moResource, $route) {
// For brevify:
var id = $route.current.params.id;
return {
mainItem: moResource('/foo/:id', {id: '@id'}).get({id: id}),
subItems: moResource('/bar/:id', {id: '@id'}).query({foo: id})
};
}]
});
In your CRUD schema template, refer to $ctrl.data.mainItem and
$ctrl.data.subItems where appropriate. If anything is "dirty" and you click
save, moUpdate will save/delete whatever it needs.
Extra data based on non-URL properties
In complicated setups, it could also be you need even more data, e.g. based on
the ticket's owner property. Of course, this isn't included in the URL and
won't be available until the ticket actually resolves.
Angular resolve keys don't actually resolves until either something returns a
value, or a returned promise is resolved. To accomplish this, you'll want to
wrap your data object in a promise (see $q in the AngularJS documentation)
and resolve that with data when you're ready.
Slightly pseudocode to give you an idea:
//...
data: ['moResource', '$route', '$q', function (moResource, $route, $q) {
var promise = $q.deferred;
var data = {};
data.mainItem = moResource('/some/url/:id').get($route.current.params.id);
data.mainItem.$promise.then(function () {
data.subItem = moResource('/another/url/:id').get({id: data.mainItem.property});
promise.resolve(data);
});
return promise;
}]
The read-only $promise property expost by models is the same promise as the
underlying Angular resource uses.
Setting defaults
When subitems are related to a "main" object, you'll need a way to persist
certain defaults to the child objects. An example would be an admin page for a
User, who has Tweets where each tweet obviously has a parameter like
owner. Just put that as an initial value when you push:
<a href ng-click="$ctrl.data.tweets.push({owner: $ctrl.data.owner.id})">Add new tweet</a>
<div ng-repeat="tweet in $ctrl.data.tweets">
...other HTML for admining a tweet...
</div>
If this is something common or you need to attach entire complex objects based
on other things like URL parameters, you could also override/extend the push
method on the resolved array in a custom controller of course. Javascript is
cool like that. It would look something like this:
app.component('awesomeFoo', {
// template, bindings etc.
controller: function () {
this.data.customList.push = function (newitem) {
// Assuming this function returns some sub-object or calculates
// a specific value or whatever:
newitem.customProperty = myComplexHandler(newitem);
[].push.call(this, newitem);
};
};
});
To pass defaults from one page to another (e.g. an "add subitem" link that points to a separate component) you may pass a "data" property in the search string of your URL:
<a ng-href="#/{{ $root.Language.current }}/my_component/create/?data={"item":{"property":value}}">
click to add a subitem!
</a>
Remember to HTML-encode the double quotes! They are needed for it to be valid JSON.
If you find yourself doing this a lot you probably want to write a simple function or filter for this, e.g.
angular.module('myAdmin', ['monad'])
// ...
.filter('json', object => angular.toJson(object));
...and use it like so:
<a ng-href="#/{{ $root.Language.current }}/my_component/create/?data={{ {item: {property: 'value'}}|json }}">
click to add a subitem!
</a>
Each key/value in data is applied to the resolved data property on the
update controller, if the key exists at the root. I.e., in the above examples
the data is only applied if data has a key item. Other defaults are ignored
silently. Also note that if for whatever reason an entry in data already
has a property passed as a default (from whatever source), it is also ignored.
Finally, note that after setting defaults the corresponding model is
automatically marked as "clean" again, even if for whatever reason other values
were already changed and the model is in fact "dirty".