10

I'm using several Angular JS $resource definitions all of which retrieve their base URL from a configuration service. For example:

$resource(config.baseURL() + '/api/v2/foo/:id', {id: '@id'})

$resource(config.baseURL() + '/api/v2/bar/:id', {id: '@id'})

The reason this is done is that the base URL can be changed via a query string parameter when the application is first loaded.

I figured out that (obviously in retrospect) the URL used by the $resource is initialized just once so it's possible to get a race condition where the URL for a particular $resource is initialized before the base URL query string parameter is dealt with. So I tried to change the $resource declaration to this:

$resource(':baseURL/api/v2/foo/:id', {baseURL: config.baseURL(), id: '@id'})

Unfortunately the base URL is getting escaped – the // is converted to %2F%2F – so the whole URL then doesn't work properly.

Is there any way to suppress the escaping for that parameter? (or maybe a better way to solve the problem in general)?

6
  • Why does your baseURL even change? Commented Nov 22, 2013 at 10:18
  • 1
    It's a tactical solution to supporting multiple environments. i.e. I can easily point an instance at a staging API if I need to debug something. Not ideal & I'm looking at a longer term solution. Commented Nov 22, 2013 at 10:28
  • You can try to embed the http:// part into the first parameter and pass the rest of the part from config.baseURL() and see if it works. Commented Nov 22, 2013 at 10:58
  • I could except that I might want to switch the scheme to HTTPS Commented Nov 25, 2013 at 11:34
  • Is there already an answer? I'd like to prototype an application with canned and afterwards implement the API in Rails, so this would be extremely useful. Commented Dec 25, 2013 at 17:20

4 Answers 4

2

Another way you can tackle this is use a provider and config it in the config stage.

Here is an example of something similar I did a while back.

.provider('Environment', function () {
    var environments = {
        dev: {
            root: 'http://localhost',
            port: 3000,
            api: '/api',
            version: 'v1'
        }
    };
    var selectedEnv = 'dev';
    var self = this;

    this.setEnvironments = function (envs) {
        if (!Object.keys(envs).length)
            throw new Error('At least one environment is required!');

        environments = envs;
    };

    this.setActive = function (env) {
        if (!environments[env])
            throw new Error('No such environment present: ' + env);

        selectedEnv = env;
        return self.getActive();
    };

    this.getEnvironment = function (env) {
        if (!env)
            throw new Error('No such environment present: ' + env);
        return environments[env];
    };

    this.getActive = function () {
        if (!selectedEnv)
            throw new Error('You must configure at least one environment');

        return environments[selectedEnv];
    };

    this.getApiRoute = function () {
        var active = self.getActive();
        return active.root + (active.port ? ':' + active.port : '') +
            active.api + (active.version ? '/' + active.version : '');
    };

    this.$get = [function () {
        return self;
    }];
})

Then in the config phase:

.config(function (EnvironmentProvider) {

    EnvironmentProvider.setEnvironments({
        dev: {
            root: 'http://10.0.0.3',
            api: '/api',
            version: 'v1'
        },
        localonly: {
            root: 'http://localhost',
            api: '/api',
            version: 'v1'
        },
        prod: {
            root: 'https://myapp.mybackend.com',
            api: '/api',
            version: 'v1'
        }
    });

    //Set prod as the active schema
    EnvironmentProvider.setActive('prod');
});

Later in some controller/service/factory:

.factory('API',function($resource, Environment){
 return {
  User: $resource(Environment.getApiRoute() + '/users/:id', {id: '@_id'}),
  OtherResource: $resource(Environment.getApiRoute() + '/otherresource/:id', {id: '@_id'})
 }
});
Sign up to request clarification or add additional context in comments.

1 Comment

This is a great solution !
0

Why not make use of the $location service?

For example, how about the following to handle the base url, and, if the application is running from localhost, include the port number? Additionally, be able to include either http or https based on the current URL?

var host = $location.host();
if (host === "localhost")
  host += ":" + $location.port();
var url = $location.protocol() + "://" + host + "/whateverElseYouWantInThePath";

and then use url where you need it?

Comments

0

From the Definition of resource,

  • @param {string} url A parametrized URL template with parameters prefixed by : as in
  • /user/:username. If you are using a URL with a port number (e.g.
  • http://example.com:8080/api), you'll need to escape the colon character before the port
  • number, like this: djResource('http://example.com\\:8080/api').

So you have to define your config.baseURL() as follows,

config.baseUrl = function(){
   return 'http://server.com\\:port/rest_part/';
}

Comments

0

Here is a horrible but working workaround. Instead of...

$resource(config.baseURL() + '/api/v2/foo/:id', {id: '@id'})

... which is only evaluated once, use an object that implements String-methods that ngResource evaluates before each request:

var url = {};
url.value = function() {return config.baseURL() + '/api/v2/foo/:id'};
url.split = function (separator,limit) { return url.value().split(separator,limit) };
url.replace = function (match, other) { return url.value().replace(match, other) };
url.toString = function() { return url.value(); }
$resource(url, {id: '@id'})

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.