9

I've been using custom actions in a few repositories. And up until now I only had to specify the url and the method.

For example:

updatePassword: {
  url: ENV.NITRO_PROJECT_REST_URL + '/admins/:adminId/password',
  method: 'PUT'
}

But then, I had to code a custom action that had, not one, but two path parameters:

technicianModule.controller('technician.teamCtrl',
  ['$scope', '$state', '$stateParams', 'CommonService', 'TechnicianService', 'TeamService', 'TeamTechnicianService',
  function($scope, $state, $stateParams, CommonService, TechnicianService, TeamService, TeamTechnicianService) {

    $scope.add = function(teamId) {
      TeamTechnicianService.add(teamId, $stateParams.technicianId, function() {
        TeamService.get(teamId, function(data) {
          $scope.teams.push(data);
          $scope.unassignedTeams.splice(CommonService.getResourceIndex($scope.unassignedTeams, data), 1);
        });
      });
    };

  }
]);

teamModule.factory('TeamTechnicianService',
  ['RESTService',
  function(RESTService) {
    var factory = {};

    factory.add = function(teamId, technicianId, callback) {
      return RESTService.TeamTechnician.add({teamId: teamId, technicianId: technicianId}).$promise.then(callback);
    }

    return factory;
  }
]);

So I first coded it like:

TeamTechnician: $resource(ENV.NITRO_PROJECT_REST_URL + '/teamtechnicians/:teamtechnicianId', {}, {
add: {
  url: ENV.NITRO_PROJECT_REST_URL + '/teamtechnicians/:teamId/:technicianId',
  method: 'POST'
}

But it would not work. The parameters were not passed in.

After a few tries I found out it worked when adding some parameter definition, right before the custom action definition.

It had to be like:

TeamTechnician: $resource(ENV.NITRO_PROJECT_REST_URL + '/teamtechnicians/:teamtechnicianId', {
  teamId: '@teamId',
  technicianId: '@technicianId'
}, {
add: {
  url: ENV.NITRO_PROJECT_REST_URL + '/teamtechnicians/:teamId/:technicianId',
  method: 'POST'
}

Note the presence of:

teamId: '@teamId',
technicianId: '@technicianId'

My understanding was then that in a $resource definition, a custom action that has more than one path parameter, requires them to be defined with @ signs.

And not when it has only one.

Why is that ?

And why can't the path parameters be declared in the custom action instead of above in the resource ?

Stephane
  • 11,836
  • 25
  • 112
  • 175

2 Answers2

27

The parameters can be declared per custom action.
The default parameters are what their name implies: default parameters (as in: "used in case other parameters are not provided").

The use of '@' (either in default parameters or in action parameters) is not mandatory.
It is provided as a convenience and has a special meaning. paramKey: '@someProp' means:
"For methods that have a request body (e.g. POST, PUT etc), if I do not explicitly provide a value for the parameter paramKey, please look into my data object for a property named someProp and use its value as the value for the paramKey parameter."


Note that when you use a class method, you have to explicitly provide the data object:

SomeResourceClass.save({.../* data object */...});

When you use an instance method, the instance itself acts as the data object:

var instance = SomeResourceClass.get(...);
instance.$save(); /* `instance` will act as the data object. */

See, also, this short demo.


UPDATE:

It seems like you want to call the following custom action:

add: {
    url: ENV.NITRO_PROJECT_REST_URL + '/teamtechnicians/:teamId/:technicianId',
    method: 'POST'
}

Trying to call it like this <ResourceClass>.add({teamId: teamId, technicianId: technicianId}) does not work as expected, as it interpret the (intended to be) params object as a data object.

From the ngResource documentation, the method signatures for "non-GET" methods (like yours) are:

  • non-GET "class" actions: Resource.action([parameters], postData, [success], [error])
  • non-GET instance actions: instance.$action([parameters], [success], [error])

From the above exerpt, it is clear that if you only pass one object in "class" action call, then it is interpreted as the data object (the request's payload). Additionally, if you have @-prefixed default parameters, then the URL parameters are going to get resolved against that data object (which is why it worked with default parameters).


In order for Angular to interpret the params object as a params (and not data object) and since the data param is mandatory, you need to call it like this:

<ResourceClass>.add({teamId: teamId, technicianId: technicianId}, {})

(Alternatively, you could be using a TeamTechnician instance, but that's another story.)

gkalpak
  • 47,844
  • 8
  • 105
  • 118
  • 5
    Your explanation of the default parameters helps more than the official documentation. – Stephane Sep 19 '14 at 14:26
  • All right, parameters can be declared per custom action. But why do I need parameters for my POST and not for my PUT points ? – Stephane Sep 19 '14 at 14:28
  • @StephaneEybert: POST and PUT methods are handle identically, so there shouldn't be any difference. Can you provide a fiddle or at least an **[SSCCE](http://sscce.org)** so I can reproduce the problem you are facing ? (BTW, if this answer helped you don't forget to upvote and/or accept it.) – gkalpak Sep 19 '14 at 18:51
  • @StephaneEybert: This comment of mine to a similar issue might shed some light on what is going on with default params/action params/'@'-prefixed params: https://github.com/angular/angular.js/issues/8948#issuecomment-55521687 – gkalpak Sep 19 '14 at 18:57
  • Thanks for these nice explanations. It's kind of you all. But I'm still left with my original question. – Stephane Sep 19 '14 at 22:03
  • @StephaneEybert: Like I said. Your question is based on "your understanding" (as you say), but your assumptions are wrong, thus the first question is void. I think I answered the second question. Since you obviously have run into a situation that caused you to make some wrong assumptions, if you post some code (e.g. a reproduction or an example of what seems to back your assumptions) I will gladly look into it and enlighten you :) (Or maybe you have stumbled upon a bug that needs to be tracked down and squashed.) – gkalpak Sep 20 '14 at 13:42
  • I'm only with my phone today, no computer. Sorry about that. I underdtand my first question was based on a wrong assumption, or rather, misleading observation. Well i still observe it and that is the reason I use this feature of default parameters. About my second question I didn't see in the official documentation that it was possible. I'm of course happy to up vote and accept answers. The only thing is I'm still using this work around. – Stephane Sep 20 '14 at 15:43
  • I'l try to post more code asap. In the meantime can someone confirm they can call a post or put with two path parameters without using default parameters at all? Thanks for bearing with me. – Stephane Sep 20 '14 at 15:48
  • @StephaneEybert: Ehm...have you seen the demo I provided ? It features a call to a POST method with two path parameters without using default parameters :) In the meantime, we are waiting for your code... – gkalpak Sep 20 '14 at 19:21
  • Thanks for your demo, I see the parameters are set up within the custom action block and not before. It's indeed better than in my case where they are set up above in the resource block. But is there a way to do without these default parameters ? My call does pass the two parameters and I don't actually need, from a design stand point, any default parameters there. Thanks for the cool demo. The alert interceptor shows the effort. That is nice. – Stephane Sep 21 '14 at 20:52
  • @StephaneEybert: If you want to to always pass the parameters explicitly, then there is no need for default parameters (neither in the resource, nor in the actions). – gkalpak Sep 21 '14 at 20:56
  • Yes, I know, that's why I've been posting my question :-) And that's how I tried at first. See, my other custom action, the one with only one path parameter, it does not use any default parameter. I tried to do it that way too, for this custom action, the one with the two path parameters, which is giving us trouble here. But it wouldn't fly. And so I tried all sorts of things to get that custom action calling my server. And it happened that using default parameters was the way. After this discussion, I understand I may be doing something wrong in the way I call the custom action. – Stephane Sep 21 '14 at 21:01
  • @StephaneEybert: Probably. That's why I tell you, if you show the code that isn't working, I might be able to spot the problem (if any). – gkalpak Sep 21 '14 at 21:04
  • I added some more code, the controller and the calling service. Let me know if there is anything more I should give. – Stephane Sep 21 '14 at 21:19
  • I just tried with the default parameters set up in the custom action block, like your demo shows, and it works. It sure is better than having them above in the resource. – Stephane Sep 21 '14 at 21:50
  • And now, another try, this time without any default parameters, that is, like: add: { url: ENV.NITRO_PROJECT_REST_URL + '/teamtechnicians/:teamId/:technicianId', method: 'POST' } then it gives a 400 on the request POST /nitro-project-rest/teamtechnicians HTTP/1.1 with payload {"teamId":1,"technicianId":"3"} We can see the url has no path parameters set in. – Stephane Sep 21 '14 at 21:58
  • Maybe my understanding was simply wrong, and the params attribute in the custom action is required to extract them from the payload. – Stephane Sep 21 '14 at 22:00
  • @StephaneEybert :Hmm...but wait ! What is the payload that you are posting ? Now you got me confused again ? – gkalpak Sep 21 '14 at 22:07
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/61640/discussion-between-expertsystem-and-stephane-eybert). – gkalpak Sep 21 '14 at 22:08
  • Sorry !! I made the mistake of accepting the wrong one yesterday. Glad you saw that. Thanks again ! – Stephane Sep 22 '14 at 08:37
3

When you define a $resource the definition looks like this

$resource(url, [paramDefaults], [actions]);

The second parameter paramDefaults is just there to provide default values. The @ syntax is used to derive the parameter value from the payload of a PUT or POST request.

You can always provide parameter values while actually invoking the resource action. If you don't then the default parameter values are taken and if that too are not there then the fragment is removed.

What can be parameterized needs to be define on $resource using the : syntax and same goes for what the default value is. For the action method they just take the same $http config object.

Chandermani
  • 42,589
  • 12
  • 85
  • 88
  • Thanks but my original question was why my PUT custom action does not need any path parameter and my POST custom action needs them. – Stephane Sep 19 '14 at 14:30
  • That is how you call the methods and pass parameters. If parameters are not passed then default values from paramDefaults are passed. – Chandermani Sep 19 '14 at 15:28
  • I don't need default values. I do pass parameters. Only, these are not seen. My original question stands. – Stephane Sep 19 '14 at 17:12
  • I only used default parameters because suddenly things worked. Not by design nor choice. I'll try to make an online example. – Stephane Sep 19 '14 at 22:31