mgonto / restangular

AngularJS service to handle Rest API Restful Resources properly and easily
MIT License
7.87k stars 840 forks source link

Nested Services or Nested Configuration #1471

Open mtraynham opened 7 years ago

mtraynham commented 7 years ago

I've seen multiple posts about this (#139, #413, #585, #1254, #1381), but none of those provide a concrete solution. I think it really stems from the fact that some ReST API's just don't abide by the same rules throughout the path hierarchy. id fields are different, responses/post body may be wildly different.

Let's take for instance an example resource: /api/cars/{modelId}/wheels/{wheelId}

So far we have two different ID fields. From the documentation, it seems like this is should be the approach:

Restangular.withConfig(RestangularConfigurer =>
    RestangularConfigurer
        .setRestangularFields({id: 'modelId', route: 'cars'})
        .setRestangularFields({id: 'wheelId', route: 'wheels', parentResource: 'cars'})

But this does not work. Per the FAQ documentation, we need to set the getIdFromElem method.

If the ReST API also expects different information for the post body and returns different things for get/getList, the implementation starts to get unwieldy as shove everything under the top parent resource, like so:

.addResponseInterceptor((data, operation, what, url) => {
    if (what === 'cars') {
        if (operation === 'getList') {
            return data.map(name => ({name}));
        }
        if (operation === 'get') {
            const urlSplit = url.split('/');
            const name = urlSplit[urlSplit.length - 1];
            return {name, descriptor: data};
        }
    }
    if (what === 'wheels') {
        if (operation === 'getList') {
            return data.map(name => ({name}));
        }
        if (operation === 'get') {
            const urlSplit = url.split('/');
            const name = urlSplit[urlSplit.length - 1];
            return {name, descriptor: data};
        }
    }
    return data;
})

Realistically, it would make the most sense if you could create a Restangular configuration and use that to create a service under a parent.

const carWheelConfig = Restangular.withConfig(RestangularConfigurer =>
    RestangularConfigurer
        .setRestangularFields({id: 'wheelId', route: 'wheels'})
        .addResponseInterceptor((data, operation, url) => {
            if (operation === 'getList') {
                return data.map(name => ({name}));
            }
            if (operation === 'get') {
                const urlSplit = url.split('/');
                const name = urlSplit[urlSplit.length - 1];
                return {name, descriptor: data};
            }
            return data;
        });

const carConfig = Restangular.withConfig(RestangularConfigurer =>
    RestangularConfigurer
        .setRestangularFields({id: 'modelId', route: 'cars'})
        .addResponseInterceptor((data, operation, url) => {
            if (operation === 'getList') {
                return data.map(name => ({name}));
            }
            if (operation === 'get') {
                const urlSplit = url.split('/');
                const name = urlSplit[urlSplit.length - 1];
                return {name, descriptor: data};
            }
            return data;
        })
        .addElementTransformer(element, false, element => 
            Object.assign(element, {
                wheels: carWheelConfig.service(element)
            }));

The addElementTransformer for adding a wheel service to car, seems like it should work, but invoking Restangular.service(parent) removes all the prior configuration of wheelConfig.


Is there anyway to create a nested configuration/service that does not directly inherit (in this broken use case override) any configuration from the parent resource, except maybe the path building aspect?